Port: Nuclear Reactor (#5474)

This commit is contained in:
Vape
2026-04-26 21:34:23 +00:00
committed by GitHub
parent ff49183d58
commit d4daae93e3
243 changed files with 11078 additions and 5 deletions
@@ -0,0 +1,87 @@
using Robust.Shared.Map;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Client.Examine;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
namespace Content.Client._FarHorizons.Power.Generation.FissionGenerator;
public sealed class NuclearReactorSystem : SharedNuclearReactorSystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NuclearReactorComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<NuclearReactorComponent, ClientExaminedEvent>(ReactorExamined);
SubscribeLocalEvent<NuclearReactorComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnInit(EntityUid uid, NuclearReactorComponent comp, ref ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!_resourceCache.TryGetResource("/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_component_cap.rsi", out RSIResource? resource))
return;
Entity<SpriteComponent?> entSprite = (uid, sprite);
var xspace = comp.Gridbounds[0] / 32f;
var yspace = comp.Gridbounds[1] / 32f;
var xoff = comp.Gridbounds[2] / 32f;
var yoff = comp.Gridbounds[3] / 32f;
var gridWidth = comp.ReactorGridWidth;
var gridHeight = comp.ReactorGridHeight;
var xAdj = (gridWidth - 1) / 2f;
var yAdj = (gridHeight - 1) / 2f;
for (var x = 0; x < gridWidth; x++)
{
for (var y = 0; y < gridHeight; y++)
{
var layerID = _sprite.AddRsiLayer(entSprite, "empty_cap", resource.RSI);
_sprite.LayerMapSet(entSprite, FormatMap(x, y), layerID);
_sprite.LayerSetOffset(entSprite, layerID, new((xspace * (y - yAdj)) - xoff, (-yspace * (x - xAdj)) - yoff));
_sprite.LayerSetColor(entSprite, layerID, Color.Black);
}
}
}
private static string FormatMap(int x, int y) => "NuclearReactorCap" + x + "/" + y;
private void ReactorExamined(EntityUid uid, NuclearReactorComponent comp, ClientExaminedEvent args) => Spawn(comp.ArrowPrototype, new EntityCoordinates(uid, 0, 0));
private void OnAppearanceChange(EntityUid uid, NuclearReactorComponent comp, ref AppearanceChangeEvent args)
{
for (var x = 0; x < comp.ReactorGridWidth; x++)
{
for (var y = 0; y < comp.ReactorGridHeight; y++)
{
if(comp.VisualData.TryGetValue(new(x,y), out var data))
UpdateRodAppearance(uid, FormatMap(x,y), data.cap, data.color);
else
UpdateRodAppearance(uid, FormatMap(x, y), "empty_cap", Color.Black);
}
}
}
private void UpdateRodAppearance(EntityUid uid, string map, string state, Color color)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
Entity<SpriteComponent?> entSprite = (uid, sprite);
if (!_sprite.LayerMapTryGet(entSprite, map, out var layer, false))
return;
_sprite.LayerSetRsiState(entSprite, layer, state);
_sprite.LayerSetColor(entSprite, layer, color);
}
}
@@ -0,0 +1,34 @@
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client._FarHorizons.Power.Generation.FissionGenerator;
public sealed class ReactorPartSystem : SharedReactorPartSystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReactorPartComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<ReactorPartComponent, ComponentInit>(OnComponentInit);
}
private void OnAppearanceChange(EntityUid uid, ReactorPartComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
// Re-enable if/when there are multiple sprites
//if (!_sprite.LayerMapTryGet((uid, args.Sprite), ReactorCapVisualLayers.Sprite, out var layer, false))
// return;
_sprite.LayerSetColor((uid, args.Sprite), 0, _proto.Index(component.Material).Color);
}
private void OnComponentInit(Entity<ReactorPartComponent> ent, ref ComponentInit args)
=> _sprite.LayerSetColor((ent.Owner, EntityManager.GetComponent<SpriteComponent>(ent.Owner)), 0, _proto.Index(ent.Comp.Material).Color);
}
@@ -0,0 +1,147 @@
using Robust.Shared.Map;
using Robust.Client.GameObjects;
using Content.Shared.Repairable;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Client.Popups;
using Content.Client.Examine;
using Robust.Client.Animations;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Popups;
namespace Content.Client._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/turbine.dm
public sealed class TurbineSystem : SharedTurbineSystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
private readonly float _threshold = 1f;
private float _accumulator = 0;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TurbineComponent, ClientExaminedEvent>(TurbineExamined);
SubscribeLocalEvent<TurbineComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<TurbineComponent, ItemSlotInsertAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<TurbineComponent, ItemSlotEjectAttemptEvent>(OnEjectAttempt);
}
protected override void OnRepairTurbineFinished(EntityUid uid, TurbineComponent comp, ref RepairDoAfterEvent args)
{
if (args.Cancelled)
return;
_popupSystem.PopupClient(Loc.GetString("turbine-repair", ("target", uid), ("tool", args.Used!)), uid, args.User);
}
private void TurbineExamined(EntityUid uid, TurbineComponent comp, ClientExaminedEvent args) => Spawn(comp.ArrowPrototype, new EntityCoordinates(uid, 0, 0));
#region Animation
private void OnAnimationCompleted(EntityUid uid, TurbineComponent comp, ref AnimationCompletedEvent args) => PlayAnimation(uid, comp);
public override void FrameUpdate(float frameTime)
{
_accumulator += frameTime;
if (_accumulator >= _threshold)
{
AccUpdate();
_accumulator = 0;
}
}
private void AccUpdate()
{
var query = EntityQueryEnumerator<TurbineComponent>();
while (query.MoveNext(out var uid, out var component))
{
// Makes sure the anim doesn't get stuck at low RPM
PlayAnimation(uid, component);
}
}
private void PlayAnimation(EntityUid uid, TurbineComponent comp)
{
if (!TryComp<SpriteComponent>(uid, out var sprite) || !_sprite.TryGetLayer((uid,sprite), TurbineVisualLayers.TurbineSpeed, out var layer, false))
return;
var state = "speedanim";
if (comp.RPM < 1)
{
_animationPlayer.Stop(uid, state);
_sprite.LayerSetRsiState(layer, "turbine");
comp.AnimRPM = -comp.BestRPM; // Primes it to start the instant it's spinning again
return;
}
if (Math.Abs(comp.RPM - comp.AnimRPM) > comp.BestRPM * 0.1)
_animationPlayer.Stop(uid, state); // Current anim is stale, time for a new one
if (_animationPlayer.HasRunningAnimation(uid, state))
return;
comp.AnimRPM = comp.RPM;
var layerKey = TurbineVisualLayers.TurbineSpeed;
var time = 0.5f * comp.BestRPM / comp.RPM;
var timestep = time / 12;
var animation = new Animation
{
Length = TimeSpan.FromSeconds(time),
AnimationTracks =
{
new AnimationTrackSpriteFlick
{
LayerKey = layerKey,
KeyFrames =
{
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_00", 0),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_01", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_02", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_03", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_04", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_05", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_06", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_07", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_08", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_09", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_10", timestep),
new AnimationTrackSpriteFlick.KeyFrame("turbinerun_11", timestep)
}
}
}
};
_sprite.LayerSetVisible(layer, true);
_animationPlayer.Play(uid, animation, state);
}
#endregion
private void OnEjectAttempt(EntityUid uid, TurbineComponent comp, ref ItemSlotEjectAttemptEvent args)
{
if (args.Cancelled)
return;
if (comp.RPM < 1)
return;
args.Cancelled = true;
}
private void OnInsertAttempt(EntityUid uid, TurbineComponent comp, ref ItemSlotInsertAttemptEvent args)
{
if (args.Cancelled)
return;
if (comp.RPM < 1)
return;
args.Cancelled = true;
}
}
@@ -0,0 +1,77 @@
using Robust.Client.UserInterface;
using JetBrains.Annotations;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
namespace Content.Client._FarHorizons.Power.UI;
/// <summary>
/// Initializes a <see cref="NuclearReactorWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class NuclearReactorBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables]
private NuclearReactorWindow? _window;
public NuclearReactorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
EntityUid? reactorUid = null;
if (_entityManager.TryGetComponent<NuclearReactorMonitorComponent>(Owner, out var reactorMonitorComponent))
{
if (!_entityManager.TryGetEntity(reactorMonitorComponent.reactor, out reactorUid) || reactorUid == null
|| !_entityManager.TryGetComponent<NuclearReactorComponent>(reactorUid, out var monitoredReactorComponent) || monitoredReactorComponent.Melted)
return;
}
else if (!_entityManager.TryGetComponent<NuclearReactorComponent>(Owner, out var reactorComponent) || reactorComponent.Melted)
return;
base.Open();
_window = this.CreateWindow<NuclearReactorWindow>();
if (_entityManager.EntityExists(reactorUid))
_window.SetEntity(reactorUid.Value, Owner);
else
_window.SetEntity(Owner);
_window.ItemActionButtonPressed += OnActionButtonPressed;
_window.EjectButtonPressed += OnEjectButtonPressed;
_window.ControlRodModify += OnControlRodModify;
Update();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (state is not NuclearReactorBuiState reactorState)
return;
_window?.Update(reactorState);
}
private void OnActionButtonPressed(Vector2d vector)
{
if (_window is null ) return;
SendPredictedMessage(new ReactorItemActionMessage(vector));
}
private void OnEjectButtonPressed()
{
if (_window is null) return;
SendPredictedMessage(new ReactorEjectItemMessage());
}
private void OnControlRodModify(float amount)
{
if (_window is null) return;
SendPredictedMessage(new ReactorControlRodModifyMessage(amount));
}
}
@@ -0,0 +1,150 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="690 445" Title="Nuclear Reactor">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<!--Main Content-->
<BoxContainer Name="MainContent" Orientation="Horizontal" VerticalExpand="True" Margin="4">
<!--Left Panel-->
<BoxContainer Name="LeftPanel" Orientation="Vertical">
<!--Insert/remove controls-->
<BoxContainer Name="Controls" Orientation="Vertical">
<GridContainer Name="ControlGrid" Columns="3" Margin="2 2 2 2">
<PanelContainer/>
<Button Name="YDecrement" Disabled="true">
<TextureRect SetSize="16 16" TextureScale="0.5 0.5" Margin="0 2 0 2"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/arrow_up.png"/>
</Button>
<PanelContainer/>
<Button Name="XDecrement" Disabled="true">
<TextureRect SetSize="16 16" TextureScale="0.5 0.5" Margin="0 2 0 2"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/arrow_left.png"/>
</Button>
<Label Name="TargetPos" Text="0,0" StyleClasses="LabelKeyText" HorizontalExpand="True" HorizontalAlignment="Center"/>
<Button Name="XIncrement">
<TextureRect SetSize="16 16" TextureScale="0.5 0.5" Margin="0 2 0 2"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/arrow_right.png"/>
</Button>
<PanelContainer/>
<Button Name="YIncrement">
<TextureRect SetSize="16 16" TextureScale="0.5 0.5" Margin="0 2 0 2"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/arrow_down.png"/>
</Button>
</GridContainer>
<controls:StripeBack>
<Button Name="ItemAction" Text="{Loc comp-nuclear-reactor-ui-insert-button}"/>
<Label Name="ItemActionLock" Text="{Loc comp-nuclear-reactor-ui-locked}" Visible="false" HorizontalAlignment="Center" VerticalAlignment="Center" />
</controls:StripeBack>
</BoxContainer>
<PanelContainer SetHeight="2" VerticalAlignment="Top" SetWidth="150" Margin="2 5" />
<!--Target info-->
<BoxContainer Name="TargetData" Orientation="Vertical" VerticalExpand="True">
<BoxContainer Name="TargetInfo" Orientation="Vertical">
<Label Name="TargetName" Text="Rod" ClipText="True"/>
<GridContainer Name="TargetTemperatureGrid" Columns="2" Margin="2 2 2 2">
<Label Text="T: " StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Button Name="TargetTemperature" Text="20C" HorizontalAlignment="Right"/>
</GridContainer>
<GridContainer Name="TargetNRadiationGrid" Columns="2" Margin="2 2 2 2">
<Label Text="N: " StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="TargetNRadiation" Text="0" HorizontalAlignment="Right"/>
</GridContainer>
<GridContainer Name="TargetRadiationGrid" Columns="2" Margin="2 2 2 2">
<Label Text="R: " StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="TargetRadiation" Text="0" HorizontalAlignment="Right"/>
</GridContainer>
<GridContainer Name="TargetSpentGrid" Columns="2" Margin="2 2 2 2">
<Label Text="S: " StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="TargetSpent" Text="0" HorizontalAlignment="Right"/>
</GridContainer>
</BoxContainer>
</BoxContainer>
<!--"Shelf"-->
<BoxContainer Name="Shelf" Orientation="Vertical">
<Button Name="EjectItem" Text="{Loc comp-nuclear-reactor-ui-eject-button}" />
<Label Name="ItemName" Text="empty" ClipText="True"/>
</BoxContainer>
</BoxContainer>
<!--Reactor display-->
<PanelContainer Name="GridVisuals" StyleClasses="Inset" HorizontalExpand="True" Margin="5 5 5 5">
<BoxContainer Orientation="Vertical">
<Button Name="ChangeViewButton" Text="{Loc comp-nuclear-reactor-ui-view-change}"/>
<Label Name="ViewLabel" Text="{Loc comp-nuclear-reactor-ui-view-temp}" StyleClasses="LabelKeyText" />
<GridContainer Name="ReactorGrid" Columns="7" VerticalExpand="True" Margin="2 2 2 2">
</GridContainer>
</BoxContainer>
</PanelContainer>
<!--Right Panel-->
<BoxContainer Name="RightPanel" Orientation="Vertical">
<Label Name="StatusPanelLabel" Text="{Loc comp-nuclear-reactor-ui-status-panel}" StyleClasses="LabelKeyText" />
<BoxContainer Name="StatusIndicators" Orientation="Vertical">
<BoxContainer Name="ReactorTemp" Orientation="Horizontal" HorizontalExpand="True">
<Label Name="ReactorTempLabel" Text="{Loc comp-nuclear-reactor-ui-reactor-temp}" StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="ReactorTempValue" Text="20C" HorizontalAlignment="Right"/>
</BoxContainer>
<ProgressBar Name="ReactorTempBar" MaxValue="2000" SetSize="150 5" Margin="5" />
<PanelContainer SetHeight="2" VerticalAlignment="Top" SetWidth="200" Margin="2 5" />
<BoxContainer Name="ReactorRads" Orientation="Horizontal">
<Label Name="ReactorRadsLabel" Text="{Loc comp-nuclear-reactor-ui-reactor-rads}" StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="ReactorRadsValue" Text="0" HorizontalAlignment="Right"/>
</BoxContainer>
<ProgressBar Name="ReactorRadsBar" MaxValue="50" SetSize="150 5" Margin="5" />
<PanelContainer SetHeight="2" VerticalAlignment="Top" SetWidth="200" Margin="2 5" />
<BoxContainer Name="ReactorTherm" Orientation="Horizontal" HorizontalExpand="True">
<Label Name="ReactorThermLabel" Text="{Loc comp-nuclear-reactor-ui-reactor-therm}" StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="ReactorThermValue" Text="0" HorizontalAlignment="Right"/>
</BoxContainer>
<ProgressBar Name="ReactorThermBar" MaxValue="10000000" SetSize="150 5" Margin="5" />
<PanelContainer SetHeight="2" VerticalAlignment="Top" SetWidth="200" Margin="2 5" />
<BoxContainer Name="ControlRodsText" Orientation="Horizontal">
<Label Name="ControlRodsLabel" Text="{Loc comp-nuclear-reactor-ui-reactor-control}" StyleClasses="StatusFieldTitle" HorizontalExpand="True"/>
<Label Name="ControlRodsValue" Text="100%" HorizontalAlignment="Right"/>
</BoxContainer>
<BoxContainer Name="ControlRods" Orientation="Horizontal">
<ProgressBar Name="ControlRodsActual" MaxValue="2" SetSize="5 100" Margin="5" Vertical="True" />
<ProgressBar Name="ControlRodsSet" MaxValue="2" SetSize="5 100" Margin="5" Vertical="True" />
<controls:StripeBack>
<BoxContainer Name="ControlRodsButtons" Orientation="Vertical">
<Button Name="ControlRodsInsertLarge" Text="+ +"/>
<Button Name="ControlRodsInsert" Text=" + "/>
<Button Name="ControlRodsRemove" Text=" - "/>
<Button Name="ControlRodsRemoveLarge" Text="- -"/>
</BoxContainer>
<Label Name="ControlRodsLock" Text="{Loc comp-nuclear-reactor-ui-locked}" Visible="false" HorizontalAlignment="Center" VerticalAlignment="Center" />
</controls:StripeBack>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!--Footer-->
<BoxContainer Name="Footer" Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc comp-nuclear-reactor-ui-footer-left}" StyleClasses="WindowFooterText" />
<Label Text="{Loc comp-nuclear-reactor-ui-footer-right}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,402 @@
using System.IO;
using Content.Client.UserInterface.Controls;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Atmos;
using Content.Shared.Lock;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client._FarHorizons.Power.UI;
/// <summary>
/// Client-side UI used to view a nuclear reactor.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class NuclearReactorWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = null!;
private readonly LockSystem _lock;
private readonly Dictionary<Vector2i, StyleBoxFlat> _reactorGrid = [];
private readonly Dictionary<Vector2i, TextureRect> _reactorRect = [];
private readonly Dictionary<Vector2i, Button> _reactorButton = [];
private readonly StyleBoxFlat _temperatureBar = new(Color.Black);
private readonly StyleBoxFlat _radiationBar = new(Color.Black);
private readonly StyleBoxFlat _powerBar = new(Color.Black);
private Dictionary<Vector2i, ReactorSlotBUIData> _data = [];
private int _gridWidth = 0;
private int _gridHeight = 0;
private byte _displayMode = 1<<0;
private enum DisplayModes : byte
{
Temperature = 1 << 0,
Neutron = 1 << 1,
Target = 1 << 2,
Fuel = 1 << 3
}
private byte _temperatureMode = 1 << 0;
private enum TemperatureModes : byte
{
Celcius = 1 << 0,
Kelvin = 1 << 1,
// Fahrenheit = 1 << 2
}
private int _targetX = 0;
private int _targetY = 0;
public event Action<Vector2d>? ItemActionButtonPressed;
public event Action? EjectButtonPressed;
public event Action<float>? ControlRodModify;
private EntityUid _reactor;
private bool _isMonitor = false;
private EntityUid _monitor;
private readonly float _repeatDelay = 0.5f;
private readonly Dictionary<Button, float> _repeatQueue = [];
public NuclearReactorWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_lock = _entityManager.System<LockSystem>();
ReactorTempBar.ForegroundStyleBoxOverride = _temperatureBar;
ReactorRadsBar.ForegroundStyleBoxOverride = _radiationBar;
ReactorThermBar.ForegroundStyleBoxOverride = _powerBar;
ChangeViewButton.OnPressed += _ => OnChangeViewButtonPressed();
XIncrement.OnPressed += _ => MoveTarget(1, 0);
XDecrement.OnPressed += _ => MoveTarget(-1, 0);
YIncrement.OnPressed += _ => MoveTarget(0, 1);
YDecrement.OnPressed += _ => MoveTarget(0, -1);
ItemAction.OnPressed += _ => ItemActionButtonPressed?.Invoke(new(_targetY, _targetX));
EjectItem.OnPressed += _ => EjectButtonPressed?.Invoke();
ControlRodsInsertLarge.OnPressed += _ => AdjustControlRods(0.1f);
ControlRodsInsertLarge.OnButtonDown += _ => _repeatQueue.Add(ControlRodsInsertLarge, _repeatDelay);
ControlRodsInsertLarge.OnButtonUp += _ => _repeatQueue.Remove(ControlRodsInsertLarge);
ControlRodsInsert.OnPressed += _ => AdjustControlRods(0.01f);
ControlRodsInsert.OnButtonDown += _ => _repeatQueue.Add(ControlRodsInsert, _repeatDelay);
ControlRodsInsert.OnButtonUp += _ => _repeatQueue.Remove(ControlRodsInsert);
ControlRodsRemove.OnPressed += _ => AdjustControlRods(-0.01f);
ControlRodsRemove.OnButtonDown += _ => _repeatQueue.Add(ControlRodsRemove, _repeatDelay);
ControlRodsRemove.OnButtonUp += _ => _repeatQueue.Remove(ControlRodsRemove);
ControlRodsRemoveLarge.OnPressed += _ => AdjustControlRods(-0.1f);
ControlRodsRemoveLarge.OnButtonDown += _ => _repeatQueue.Add(ControlRodsRemoveLarge, _repeatDelay);
ControlRodsRemoveLarge.OnButtonUp += _ => _repeatQueue.Remove(ControlRodsRemoveLarge);
TargetTemperature.OnPressed += _ => ChangeTemp();
}
public void Update(NuclearReactorBuiState msg)
{
_data = msg.SlotData;
ReactorTempValue.Text = FormatTemperature(msg.ReactorTemp);
ReactorTempBar.Value = msg.ReactorTemp;
_temperatureBar.BackgroundColor = GetColor(Atmospherics.T20C, ReactorTempBar.MaxValue * 0.75, msg.ReactorTemp);
ReactorRadsValue.Text = msg.ReactorRads <= msg.ReactorRadsMax ? Math.Round(msg.ReactorRads, 1).ToString() : "OVERLOAD";
ReactorRadsBar.Value = msg.ReactorRads;
_radiationBar.BackgroundColor = GetColor(0, ReactorRadsBar.MaxValue * 0.5, msg.ReactorRads);
ReactorThermValue.Text = FormatPower(msg.ReactorTherm);
ReactorThermBar.Value = msg.ReactorTherm;
_powerBar.BackgroundColor = GetSteppedColor(ReactorThermBar.MaxValue * 0.75, ReactorThermBar.MaxValue, msg.ReactorTherm);
ControlRodsValue.Text = Math.Round(msg.ControlRodActual * 50, 1).ToString() + "%";
ControlRodsActual.Value = msg.ControlRodActual;
ControlRodsSet.Value = msg.ControlRodSet;
var locktarget = _isMonitor ? _monitor : _reactor;
ControlRodsButtons.Visible = !_lock.IsLocked(locktarget);
ItemAction.Visible = !_lock.IsLocked(_reactor) && !_isMonitor;
ItemActionLock.Visible = _lock.IsLocked(_reactor) && !_isMonitor;
ControlRodsLock.Visible = _lock.IsLocked(locktarget);
Shelf.Visible = !_isMonitor;
ItemName.Text = msg.ItemName ?? "empty";
}
public void SetEntity(EntityUid reactor, EntityUid? monitor = null)
{
_reactor = reactor;
if (monitor != null)
{
_monitor = monitor.Value;
_isMonitor = true;
}
this.SetInfoFromEntity(_entityManager, _isMonitor ? _monitor : _reactor);
if (!_entityManager.TryGetComponent<NuclearReactorComponent>(_reactor, out var reactorComponent))
return;
_gridWidth = reactorComponent.ReactorGridWidth;
_gridHeight = reactorComponent.ReactorGridHeight;
ReactorGrid.Columns = _gridWidth;
ReactorTempBar.MaxValue = reactorComponent.ReactorMeltdownTemp;
ReactorRadsBar.MaxValue = reactorComponent.MaximumRadiation;
ReactorThermBar.MaxValue = reactorComponent.MaximumThermalPower;
InitReactorGrid();
}
public void InitReactorGrid()
{
for (var x = 0; x < _gridWidth; x++)
{
for (var y = 0; y < _gridHeight; y++)
{
var styleBox = new StyleBoxFlat();
var icon = new TextureRect()
{
SetSize = new(32, 32),
TexturePath = "/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/base.png"
};
var button = new Button
{
Margin = new(0),
StyleBoxOverride = new StyleBoxFlat(Color.Transparent),
ToolTip = "",
TooltipDelay = 0.5f
};
var vect = new Vector2i(x, y);
_reactorGrid.Add(vect, styleBox);
_reactorRect.Add(vect, icon);
_reactorButton.Add(vect, button);
var control = new PanelContainer
{
Margin = new Thickness(2),
PanelOverride = styleBox,
HorizontalExpand = true,
VerticalExpand = true,
};
control.AddChild(button);
var nvect = new Vector2i(y, x); // Can't be declared as a part of the line below for some reason, will break if you try
button.OnPressed += _ => SetTarget(nvect);
button.AddChild(icon);
ReactorGrid.AddChild(control);
}
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
//Update the grid
for (var x = 0; x < _gridWidth; x++)
{
for (var y = 0; y < _gridHeight; y++)
{
var vect = new Vector2i(x, y);
var exists = _data.ContainsKey(vect);
var box = _reactorGrid[vect];
switch (_displayMode)
{
case (byte)DisplayModes.Temperature:
box.BackgroundColor = GetColor(293.15, 1200, exists ? _data[vect].Temperature : 0);
ViewLabel.Text = Loc.GetString("comp-nuclear-reactor-ui-view-temp");
break;
case (byte)DisplayModes.Neutron:
box.BackgroundColor = GetColor(0, 7, exists ? _data[vect].NeutronCount : 0);
ViewLabel.Text = Loc.GetString("comp-nuclear-reactor-ui-view-neutron");
break;
case (byte)DisplayModes.Target:
box.BackgroundColor = y == _targetX && x == _targetY ? Color.Yellow : Color.Gray;
ViewLabel.Text = Loc.GetString("comp-nuclear-reactor-ui-view-target");
break;
case (byte)DisplayModes.Fuel:
box.BackgroundColor = Color.InterpolateBetween(Color.Gray, Color.FromHex("#22bbff"), exists && _data[vect].SpentFuel > 0 ? (float)GetFuelLevel(_data[vect]) : 0);
ViewLabel.Text = Loc.GetString("comp-nuclear-reactor-ui-view-fuel");
break;
}
var icon = exists ? _data[vect].IconName : "base";
_reactorRect[vect].TexturePath = "/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_part_inserted/" + icon + ".png";
_reactorButton[vect].ToolTip = exists && _data[vect].SpentFuel > 0
? "Fuel Level: " + (int)Math.Round(GetFuelLevel(_data[vect]) * 100) + "%"
: "";
}
}
UpdateTargetInfo();
UpdateItemAction();
// Handle repeat buttons
foreach ( var kvp in _repeatQueue)
{
if(kvp.Value > 0)
{
_repeatQueue[kvp.Key] -= args.DeltaSeconds;
continue;
}
switch (kvp.Key)
{
case var _ when kvp.Key == ControlRodsInsertLarge:
AdjustControlRods(0.1f);
break;
case var _ when kvp.Key == ControlRodsInsert:
AdjustControlRods(0.01f);
break;
case var _ when kvp.Key == ControlRodsRemove:
AdjustControlRods(-0.01f);
break;
case var _ when kvp.Key == ControlRodsRemoveLarge:
AdjustControlRods(-0.1f);
break;
}
}
}
private static Color GetColor(double pointA, double pointB, double value)
{
var mid = pointA+((pointB-pointA) / 2);
Color result;
if (value < pointA && pointA > 0)
result = Color.InterpolateBetween(Color.DarkBlue, Color.FromHex("#31843E"), (float)(value / pointA));
else if (value >= pointA && value < mid)
result = Color.InterpolateBetween(Color.FromHex("#31843E"), Color.FromHex("#BBBB00"), (float)((value - pointA) / (mid - pointA)));
else if (value >= mid && value < pointB)
result = Color.InterpolateBetween(Color.FromHex("#BBBB00"), Color.FromHex("#BB3232"), (float)((value - mid) / (pointB - mid)));
else if (value >= pointB && value < pointB * 1.4)
result = Color.InterpolateBetween(Color.FromHex("#BB3232"), Color.FromHex("#550000"), (float)((value - pointB) / ((pointB * 1.4) - pointB)));
else if (value >= pointB * 1.4)
result = Color.FromHex("#550000"); // Death.
else
result = Color.Black;
return result;
}
private static Color GetSteppedColor(double pointA, double pointB, double value)
{
Color result;
if (value > pointB)
result = Color.FromHex("#BB3232");
else if (value > pointA)
result = Color.FromHex("#BBBB00");
else
result = Color.FromHex("#31843E");
return result;
}
private void OnChangeViewButtonPressed()
{
// The bits go marching one by one...
_displayMode <<= 1;
if (_displayMode >= 1 << Enum.GetNames<DisplayModes>().Length)
_displayMode = 1 << 0;
}
private void ChangeTemp()
{
_temperatureMode <<= 1;
if (_temperatureMode >= 1 << Enum.GetNames<TemperatureModes>().Length)
_temperatureMode = 1 << 0;
}
private string FormatTemperature(double temperature) => _temperatureMode switch
{
(byte)TemperatureModes.Celcius => Math.Round(temperature - Atmospherics.T0C, 1).ToString() + "C",
(byte)TemperatureModes.Kelvin => Math.Round(temperature, 1).ToString() + "K",
// (byte)TemperatureModes.Fahrenheit => Math.Round(((temperature - Atmospherics.T0C) * 9 / 5) + 32, 1).ToString() + "F",
_ => "NaN",
};
private void UpdateTargetInfo()
{
var vect = new Vector2i(_targetY, _targetX);
if(!_data.TryGetValue(vect, out var value))
{
TargetName.Text = "empty";
TargetTemperatureGrid.Visible = TargetNRadiationGrid.Visible = TargetRadiationGrid.Visible = TargetSpentGrid.Visible = false;
return;
}
TargetName.Text = value.PartName;
TargetTemperatureGrid.Visible = value.Temperature > 0;
TargetTemperature.Text = FormatTemperature(value.Temperature);
TargetNRadiationGrid.Visible = value.NeutronRadioactivity > 0;
TargetNRadiation.Text = Math.Round(value.NeutronRadioactivity, 2).ToString();
TargetRadiationGrid.Visible = value.Radioactivity > 0;
TargetRadiation.Text = Math.Round(value.Radioactivity, 2).ToString();
TargetSpentGrid.Visible = value.SpentFuel > 0;
TargetSpent.Text = Math.Round(value.SpentFuel, 2).ToString();
}
private void UpdateItemAction()
{
if(ItemName.Text == "empty" == (TargetName.Text == "empty"))
{
ItemAction.Disabled = true;
return;
}
else
ItemAction.Disabled = false;
ItemAction.Text = TargetName.Text != "empty"
? Loc.GetString("comp-nuclear-reactor-ui-remove-button")
: Loc.GetString("comp-nuclear-reactor-ui-insert-button");
}
private void MoveTarget(int x, int y) => SetTarget(_targetX + x, _targetY + y);
private void SetTarget(Vector2i vect) => SetTarget(vect.X, vect.Y);
private void SetTarget(int x, int y)
{
_targetX = Math.Clamp(x, 0, _gridWidth - 1);
_targetY = Math.Clamp(y, 0, _gridHeight - 1);
TargetPos.Text = _targetX.ToString() +"," + _targetY.ToString();
XIncrement.Disabled = _targetX >= _gridWidth - 1;
XDecrement.Disabled = _targetX <= 0;
YIncrement.Disabled = _targetY >= _gridHeight - 1;
YDecrement.Disabled = _targetY <= 0;
}
public void SetItemName(string? itemName) => ItemName.Text = itemName ?? "empty";
private static string FormatPower(float power) => Loc.GetString("comp-nuclear-reactor-ui-therm-format", ("power", power));
private static double GetFuelLevel(ReactorSlotBUIData data) => Math.Max(1 - (data.SpentFuel / (data.SpentFuel + (data.Radioactivity * 0.5) + (data.NeutronRadioactivity * 0.25))), 0);
private void AdjustControlRods(float amount) => ControlRodModify?.Invoke(amount);
}
@@ -0,0 +1,104 @@
using Robust.Client.UserInterface;
using Robust.Client.Timing;
using JetBrains.Annotations;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Client.UserInterface;
using System.Diagnostics.CodeAnalysis;
namespace Content.Client._FarHorizons.Power.UI;
/// <summary>
/// Initializes a <see cref="TurbineWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class TurbineBoundUserInterface : BoundUserInterface, IBuiPreTickUpdate
{
[Dependency] private readonly IClientGameTiming _gameTiming = null!;
[Dependency] private readonly IEntityManager _entityManager = null!;
[ViewVariables]
private TurbineWindow? _window;
private BuiPredictionState? _pred;
private InputCoalescer<float> _flowRateCoalescer;
private InputCoalescer<float> _statorLoadCoalescer;
public TurbineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
EntityUid? turbineUid = null;
if (_entityManager.TryGetComponent<GasTurbineMonitorComponent>(Owner, out var turbineMonitorComponent))
if (!_entityManager.TryGetEntity(turbineMonitorComponent.turbine, out turbineUid) || turbineUid == null
|| !_entityManager.HasComponent<TurbineComponent>(turbineUid))
return;
base.Open();
_pred = new BuiPredictionState(this, _gameTiming);
_window = this.CreateWindow<TurbineWindow>();
if (_entityManager.EntityExists(turbineUid))
_window.SetEntity(turbineUid.Value, Owner);
else
_window.SetEntity(Owner);
_window.TurbineFlowRateChanged += val => _flowRateCoalescer.Set(val);
_window.TurbineStatorLoadChanged += val => _statorLoadCoalescer.Set(val);
Update();
}
void IBuiPreTickUpdate.PreTickUpdate()
{
if (_flowRateCoalescer.CheckIsModified(out var flowRateValue))
_pred!.SendMessage(new TurbineChangeFlowRateMessage(flowRateValue));
if (_statorLoadCoalescer.CheckIsModified(out var statorLoadValue))
_pred!.SendMessage(new TurbineChangeStatorLoadMessage(statorLoadValue));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (state is not TurbineBuiState turbineState)
return;
if (!_entityManager.TryGetComponent<TurbineComponent>(Owner, out var comp))
if(!TryGetTurbineComp(Owner, out comp))
return;
foreach (var replayMsg in _pred!.MessagesToReplay())
{
switch (replayMsg)
{
case TurbineChangeFlowRateMessage setFlowRate:
turbineState.FlowRate = Math.Clamp(setFlowRate.FlowRate, 0f, comp.FlowRateMax);
break;
case TurbineChangeStatorLoadMessage setStatorLoad:
turbineState.StatorLoad = Math.Max(setStatorLoad.StatorLoad, 1000f);
break;
}
}
_window?.Update(turbineState);
}
public bool TryGetTurbineComp(EntityUid uid, [NotNullWhen(true)] out TurbineComponent? turbineComponent)
{
turbineComponent = null;
if (!_entityManager.TryGetComponent<GasTurbineMonitorComponent>(uid, out var turbineMonitor))
return false;
if (!_entityManager.TryGetEntity(turbineMonitor.turbine, out var turbineUid) || turbineUid == null)
return false;
if (!_entityManager.TryGetComponent<TurbineComponent>(turbineUid, out var turbine))
return false;
turbineComponent = turbine;
return true;
}
}
@@ -0,0 +1,134 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="525 415" Title="Gas Turbine">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
<TabContainer Name="CTabContainer" VerticalExpand="True" Margin="1 1 1 2">
<!--Main Content-->
<BoxContainer Name="MainContent" Orientation="Horizontal" VerticalExpand="True" Margin="4">
<!--View of turbine sprite-->
<BoxContainer Name="TurbineVeiw" Orientation="Horizontal" VerticalExpand="True" Margin="4">
<SpriteView Name="EntityView" SetSize="96 160" Scale="2 2" OverrideDirection="South" Margin="15" />
</BoxContainer>
<!--RPM gauge-->
<PanelContainer Name="Speed" StyleClasses="Inset">
<BoxContainer Orientation="Vertical">
<Label Text="{Loc 'comp-turbine-ui-rpm'}" StyleClasses="LabelKeyText" />
<GridContainer Name="RPMMeter" Columns="1" VerticalExpand="True" Margin="0 0 0 0">
</GridContainer>
</BoxContainer>
</PanelContainer>
<!--Right panel-->
<BoxContainer Name="RightPanel" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10" HorizontalExpand="True" VerticalExpand="True">
<!--Indicator Lights-->
<PanelContainer Name="Indicators" StyleClasses="Inset">
<GridContainer Columns="4">
<Label Text="{Loc 'comp-turbine-ui-overspeed'}" StyleClasses="StatusFieldTitle" />
<TextureRect Name="TurbineOverspeed" SetSize="16 16" HorizontalAlignment="Left" HorizontalExpand="True"/>
<Label Text="{Loc 'comp-turbine-ui-overtemp'}" StyleClasses="StatusFieldTitle" />
<TextureRect Name="TurbineOvertemp" SetSize="16 16" HorizontalAlignment="Left" HorizontalExpand="True"/>
<Label Text="{Loc 'comp-turbine-ui-stalling'}" StyleClasses="StatusFieldTitle" />
<TextureRect Name="TurbineStalling" SetSize="16 16" HorizontalAlignment="Left" HorizontalExpand="True"/>
<Label Text="{Loc 'comp-turbine-ui-undertemp'}" StyleClasses="StatusFieldTitle" />
<TextureRect Name="TurbineUndertemp" SetSize="16 16" HorizontalAlignment="Left" HorizontalExpand="True"/>
</GridContainer>
</PanelContainer>
<!--Inputs-->
<controls:StripeBack VerticalExpand="True">
<BoxContainer Name="Inputs" Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10" HorizontalExpand="True" >
<PanelContainer StyleClasses="Inset" Margin="0 0 0 8">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-turbine-ui-flow-rate}" StyleClasses="StatusFieldTitle" HorizontalExpand="True" />
<LineEdit Name="TurbineFlowRateLabel" MinSize="70 0"/>
</BoxContainer>
<Slider Name="TurbineFlowRateSlider" />
<BoxContainer Orientation="Horizontal">
<Button Name="FlowRateDecrease" Text="-100"/>
<BoxContainer HorizontalExpand="True" />
<Button Name="FlowRateIncrease" Text="+100"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="Inset" Margin="0 0 0 8">
<BoxContainer Orientation="Vertical" >
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc comp-turbine-ui-stator-load}" StyleClasses="StatusFieldTitle" HorizontalExpand="True" />
<LineEdit Name="TurbineStatorLoadLabel" MinSize="70 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="StatorLoadDecreaseLarge" Text="-1000"/>
<Button Name="StatorLoadDecrease" Text="-100"/>
<BoxContainer HorizontalExpand="True" />
<Button Name="StatorLoadIncrease" Text="+100"/>
<Button Name="StatorLoadIncreaseLarge" Text="+1000"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
<Label Name="LockedMessage" Text="{Loc comp-turbine-ui-locked-message}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</controls:StripeBack>
</BoxContainer>
</BoxContainer>
<!--Blade / Stator info-->
<BoxContainer Name="PartInfo" Orientation="Horizontal" VerticalExpand="True" Margin="4">
<PanelContainer StyleClasses="Inset" HorizontalExpand="True" Margin="4">
<TextureRect Name="BladeTexture" SetSize="128 128" TextureScale="2 2" HorizontalAlignment="Center" VerticalAlignment="Center"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/turbine_parts/blade.png"/>
<BoxContainer Name="BladeInfo" Orientation="Vertical" HorizontalExpand="True" Margin="4">
<Label Name="BladeInfoName" Text="{Loc 'comp-turbine-ui-blade'}" StyleClasses="LabelKeyText" HorizontalAlignment="Center" HorizontalExpand="True"/>
<SpriteView Name="BladeEntityView" SetSize="128 128" Scale="2 2" OverrideDirection="South" Margin="15" />
<BoxContainer VerticalExpand="True"/>
<GridContainer Columns="2">
<Label Text="{Loc 'comp-turbine-ui-blade-integrity'}" StyleClasses="StatusFieldTitle" />
<Label Name="BladeInfoIntegrity" Text="100%" HorizontalAlignment="Right" HorizontalExpand="True"/>
<Label Text="{Loc 'comp-turbine-ui-blade-stress'}" StyleClasses="StatusFieldTitle" />
<Label Name="BladeInfoStress" Text="0%" HorizontalAlignment="Right" HorizontalExpand="True"/>
</GridContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="Inset" HorizontalExpand="True" Margin="4">
<TextureRect Name="StatorTexture" SetSize="128 128" TextureScale="2 2" HorizontalAlignment="Center"
TexturePath="/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/turbine_parts/stator.png"/>
<BoxContainer Name="StatorInfo" Orientation="Vertical" HorizontalExpand="True" Margin="4">
<Label Name="StatorInfoName" Text="{Loc 'comp-turbine-ui-stator'}" StyleClasses="LabelKeyText" HorizontalAlignment="Center" HorizontalExpand="True"/>
<SpriteView Name="StatorEntityView" SetSize="128 128" Scale="2 2" OverrideDirection="South" Margin="15" />
<BoxContainer VerticalExpand="True"/>
<GridContainer Columns="2">
<Label Text="{Loc 'comp-turbine-ui-stator-potential'}" StyleClasses="StatusFieldTitle" />
<Label Name="StatorInfoPotential" Text="0.0 W" HorizontalAlignment="Right" HorizontalExpand="True"/>
<Label Text="{Loc 'comp-turbine-ui-stator-supply'}" StyleClasses="StatusFieldTitle" />
<Label Name="StatorInfoSupply" Text="0.0 W" HorizontalAlignment="Right" HorizontalExpand="True"/>
</GridContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</TabContainer>
<!--Footer-->
<BoxContainer Name="Footer" Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc comp-turbine-ui-footer-left}" StyleClasses="WindowFooterText" />
<Label Text="{Loc comp-turbine-ui-footer-right}" StyleClasses="WindowFooterText"
HorizontalAlignment="Right" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -0,0 +1,298 @@
using Content.Client.UserInterface.Controls;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.IdentityManagement;
using Content.Shared.Lock;
using Content.Shared.Rounding;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
namespace Content.Client._FarHorizons.Power.UI;
/// <summary>
/// Client-side UI used to control a turbine.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class TurbineWindow : FancyWindow
{
// Dependencies
[Dependency] private readonly IEntityManager _entityManager = null!;
private readonly LockSystem _lock;
#region Variables
//Colors for the RPM meter. (lit)
private static readonly Color[] _speedColors = [
Color.FromHex("#BB3232"),
Color.FromHex("#BB3232"),
Color.FromHex("#BB3232"),
Color.FromHex("#C49438"),
Color.FromHex("#C49438"),
Color.FromHex("#C49438"),
Color.FromHex("#B3BF28"),
Color.FromHex("#B3BF28"),
Color.FromHex("#B3BF28"),
Color.FromHex("#6FC938"),
Color.FromHex("#C49438"),
Color.FromHex("#BB3232"),
];
//Colors for the RPM meter. (unlit)
private static readonly Color[] _speedColorsDim =
[
DimGaugeColor(_speedColors[0]),
DimGaugeColor(_speedColors[1]),
DimGaugeColor(_speedColors[2]),
DimGaugeColor(_speedColors[3]),
DimGaugeColor(_speedColors[4]),
DimGaugeColor(_speedColors[5]),
DimGaugeColor(_speedColors[6]),
DimGaugeColor(_speedColors[7]),
DimGaugeColor(_speedColors[8]),
DimGaugeColor(_speedColors[9]),
DimGaugeColor(_speedColors[10]),
DimGaugeColor(_speedColors[11]),
];
// Style boxes for the RPM meter.
private StyleBoxFlat[] _speedMeter;
private int _speedLevel;
private float _flowRate = 0;
private float _statorLevel = 0;
private EntityUid _turbine;
private bool _isMonitor = false;
private EntityUid _monitor;
private bool _suppressSliderEvents;
private bool _suppressStatorUpdate;
private bool _suppressFlowUpdate;
private readonly float _repeatDelay = 0.5f;
private readonly Dictionary<Button, float> _repeatQueue = [];
#endregion
#region Events
public event Action<float>? TurbineFlowRateChanged;
public event Action<float>? TurbineStatorLoadChanged;
#endregion
public TurbineWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_lock = _entityManager.System<LockSystem>();
InitRPMMeter();
// Handle flow rate
TurbineFlowRateLabel.OnFocusEnter += _ => _suppressFlowUpdate = true;
TurbineFlowRateLabel.OnFocusExit += _ => FlowTextChanged();
TurbineFlowRateLabel.OnTextEntered += _ => FlowTextChanged(true);
TurbineFlowRateSlider.OnValueChanged += _ =>
{
if (!_suppressSliderEvents)
TurbineFlowRateChanged?.Invoke(TurbineFlowRateSlider.Value);
};
FlowRateDecrease.OnPressed += _ => TurbineFlowRateChanged?.Invoke(_flowRate - 100);
FlowRateDecrease.OnButtonDown += _ => _repeatQueue.Add(FlowRateDecrease, _repeatDelay);
FlowRateDecrease.OnButtonUp += _ => _repeatQueue.Remove(FlowRateDecrease);
FlowRateIncrease.OnPressed += _ => TurbineFlowRateChanged?.Invoke(_flowRate + 100);
FlowRateIncrease.OnButtonDown += _ => _repeatQueue.Add(FlowRateIncrease, _repeatDelay);
FlowRateIncrease.OnButtonUp += _ => _repeatQueue.Remove(FlowRateIncrease);
// Handle stator load
TurbineStatorLoadLabel.OnFocusEnter += _ => _suppressStatorUpdate = true;
TurbineStatorLoadLabel.OnFocusExit += _ => StatorTextChanged();
TurbineStatorLoadLabel.OnTextEntered += _ => StatorTextChanged(true);
StatorLoadDecreaseLarge.OnPressed += _ => TurbineStatorLoadChanged?.Invoke(_statorLevel - 1000);
StatorLoadDecreaseLarge.OnButtonDown += _ => _repeatQueue.Add(StatorLoadDecreaseLarge, _repeatDelay);
StatorLoadDecreaseLarge.OnButtonUp += _ => _repeatQueue.Remove(StatorLoadDecreaseLarge);
StatorLoadDecrease.OnPressed += _ => TurbineStatorLoadChanged?.Invoke(_statorLevel - 100);
StatorLoadDecrease.OnButtonDown += _ => _repeatQueue.Add(StatorLoadDecrease, _repeatDelay);
StatorLoadDecrease.OnButtonUp += _ => _repeatQueue.Remove(StatorLoadDecrease);
StatorLoadIncrease.OnPressed += _ => TurbineStatorLoadChanged?.Invoke(_statorLevel + 100);
StatorLoadIncrease.OnButtonDown += _ => _repeatQueue.Add(StatorLoadIncrease, _repeatDelay);
StatorLoadIncrease.OnButtonUp += _ => _repeatQueue.Remove(StatorLoadIncrease);
StatorLoadIncreaseLarge.OnPressed += _ => TurbineStatorLoadChanged?.Invoke(_statorLevel + 1000);
StatorLoadIncreaseLarge.OnButtonDown += _ => _repeatQueue.Add(StatorLoadIncreaseLarge, _repeatDelay);
StatorLoadIncreaseLarge.OnButtonUp += _ => _repeatQueue.Remove(StatorLoadIncreaseLarge);
CTabContainer.SetTabTitle(0, Loc.GetString("comp-turbine-ui-tab-main"));
CTabContainer.SetTabTitle(1, Loc.GetString("comp-turbine-ui-tab-parts"));
return;
void FlowTextChanged(bool suppress = false)
{
if (float.TryParse(TurbineFlowRateLabel.Text, out var num) && !float.IsNaN(num))
TurbineFlowRateChanged?.Invoke(num);
_suppressFlowUpdate = suppress;
}
void StatorTextChanged(bool suppress = false)
{
if (float.TryParse(TurbineStatorLoadLabel.Text, out var num) && !float.IsNaN(num))
TurbineStatorLoadChanged?.Invoke(num);
_suppressStatorUpdate = suppress;
}
}
#region Graphics
public void SetEntity(EntityUid turbine, EntityUid? monitor = null)
{
_turbine = turbine;
if (monitor != null)
{
_monitor = monitor.Value;
_isMonitor = true;
}
this.SetInfoFromEntity(_entityManager, _isMonitor ? _monitor : _turbine);
EntityView.SetEntity(turbine);
}
[MemberNotNull(nameof(_speedMeter))]
public void InitRPMMeter()
{
_speedMeter = new StyleBoxFlat[_speedColors.Length];
for (var i = _speedColors.Length - 1; i >= 0; i--)
{
var styleBox = new StyleBoxFlat();
_speedMeter[i] = styleBox;
for (var j = 0; j < RPMMeter.Columns; j++)
{
var control = new PanelContainer
{
Margin = new Thickness(2),
PanelOverride = styleBox,
HorizontalExpand = true,
VerticalExpand = true,
};
RPMMeter.AddChild(control);
}
}
}
private static Color DimGaugeColor(Color color)
{
var hsv = Color.ToHsv(color);
hsv.Z /= 5;
return Color.FromHsv(hsv);
}
public void Update(TurbineBuiState msg)
{
UpdateIndicators(msg);
if(!_suppressFlowUpdate)
TurbineFlowRateLabel.Text = Math.Round(msg.FlowRate).ToString();
if(!_suppressStatorUpdate)
TurbineStatorLoadLabel.Text = Math.Round(msg.StatorLoad).ToString();
var locktarget = _isMonitor ? _monitor : _turbine;
Inputs.Visible = !_lock.IsLocked(locktarget);
LockedMessage.Visible = _lock.IsLocked(locktarget);
_suppressSliderEvents = true;
TurbineFlowRateSlider.MaxValue = msg.FlowRateMax;
TurbineFlowRateSlider.MinValue = msg.FlowRateMin;
TurbineFlowRateSlider.Value = msg.FlowRate;
_flowRate = msg.FlowRate;
_statorLevel = msg.StatorLoad;
_suppressSliderEvents = false;
_speedLevel = ContentHelpers.RoundToNearestLevels(msg.RPM, msg.BestRPM * 1.2, _speedMeter.Length);
var bladeExists = msg.Blade != null;
if(bladeExists)
{
BladeEntityView.SetEntity(msg.Blade!.Value);
BladeInfoName.Text = Identity.Name(_entityManager.GetEntity(msg.Blade!.Value), _entityManager);
}
BladeInfo.Visible = bladeExists;
BladeInfoIntegrity.Text = Math.Round(msg.Health * 100 / msg.HealthMax).ToString() + "%";
BladeInfoStress.Text = Math.Round(msg.RPM * 100 / (msg.BestRPM * 1.2)).ToString() + "%";
var statorExists = msg.Stator != null;
if (statorExists)
{
StatorEntityView.SetEntity(msg.Stator!.Value);
StatorInfoName.Text = Identity.Name(_entityManager.GetEntity(msg.Stator!.Value), _entityManager);
}
StatorInfo.Visible = statorExists;
StatorInfoPotential.Text = Loc.GetString("comp-turbine-ui-power", ("power", msg.PowerGeneration));
StatorInfoSupply.Text = Loc.GetString("comp-turbine-ui-power", ("power", msg.PowerSupply));
}
private void UpdateIndicators(TurbineBuiState msg)
{
var FilePath = "/Textures/_FarHorizons/Structures/Power/Generation/FissionGenerator/indicator_lamps/";
// These are just if/else statements in black magic form
TurbineOverspeed.TexturePath = msg.Overspeed ? FilePath + "redlit.png" : FilePath + "reddim.png";
TurbineOvertemp.TexturePath = msg.Overtemp ? FilePath + "redlit.png" : FilePath + "reddim.png";
TurbineStalling.TexturePath = msg.Stalling ? FilePath + "amberlit.png" : FilePath + "amberdim.png";
TurbineUndertemp.TexturePath = msg.Undertemp ? FilePath + "bluelit.png" : FilePath + "bluedim.png";
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
for (var i = 0; i < _speedMeter.Length; i++)
{
var box = _speedMeter[i];
box.BackgroundColor = _speedLevel > i ? _speedColors[i] : _speedColorsDim[i];
}
foreach (var kvp in _repeatQueue)
{
if(kvp.Value > 0)
{
_repeatQueue[kvp.Key] -= args.DeltaSeconds;
continue;
}
switch (kvp.Key)
{
case var _ when kvp.Key == FlowRateDecrease:
TurbineFlowRateChanged?.Invoke(_flowRate - 100);
break;
case var _ when kvp.Key == FlowRateIncrease:
TurbineFlowRateChanged?.Invoke(_flowRate + 100);
break;
case var _ when kvp.Key == StatorLoadDecreaseLarge:
TurbineStatorLoadChanged?.Invoke(_statorLevel - 1000);
break;
case var _ when kvp.Key == StatorLoadDecrease:
TurbineStatorLoadChanged?.Invoke(_statorLevel - 100);
break;
case var _ when kvp.Key == StatorLoadIncrease:
TurbineStatorLoadChanged?.Invoke(_statorLevel + 100);
break;
case var _ when kvp.Key == StatorLoadIncreaseLarge:
TurbineStatorLoadChanged?.Invoke(_statorLevel + 1000);
break;
}
}
}
#endregion
}
@@ -0,0 +1,252 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.DeviceLinking.Systems;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Database;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
public sealed partial class GasTurbineMonitorSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly TurbineSystem _turbineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signal = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private readonly float _threshold = 0.5f;
private float _accumulator = 0f;
private sealed class LogData
{
public TimeSpan CreationTime;
public NetEntity Turbine;
public float? SetFlowRate;
public float? SetStatorLoad;
}
private readonly Dictionary<KeyValuePair<EntityUid, EntityUid>, LogData> _logQueue = [];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasTurbineMonitorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GasTurbineMonitorComponent, NewLinkEvent>(OnNewLink);
SubscribeLocalEvent<GasTurbineMonitorComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<GasTurbineMonitorComponent, TurbineChangeFlowRateMessage>(OnTurbineFlowRateChanged);
SubscribeLocalEvent<GasTurbineMonitorComponent, TurbineChangeStatorLoadMessage>(OnTurbineStatorLoadChanged);
SubscribeLocalEvent<GasTurbineMonitorComponent, AnchorStateChangedEvent>(OnAnchorChanged);
}
private void OnMapInit(EntityUid uid, GasTurbineMonitorComponent comp, ref MapInitEvent args)
{
if (!_entityManager.TryGetComponent<DeviceLinkSinkComponent>(uid, out var sink))
return;
foreach (var source in sink.LinkedSources)
{
if (!HasComp<TurbineComponent>(source))
continue;
comp.turbine = GetNetEntity(source);
Dirty(uid, comp);
return; // The return is to make it behave such that the first connetion that's a turbine is the one chosen
}
}
private void OnNewLink(EntityUid uid, GasTurbineMonitorComponent comp, ref NewLinkEvent args)
{
if (!HasComp<TurbineComponent>(args.Source))
return;
comp.turbine = GetNetEntity(args.Source);
Dirty(uid, comp);
}
private void OnPortDisconnected(EntityUid uid, GasTurbineMonitorComponent comp, ref PortDisconnectedEvent args)
{
if (args.Port != comp.LinkingPort)
return;
comp.turbine = null;
Dirty(uid, comp);
}
public bool TryGetTurbineComp(GasTurbineMonitorComponent turbineMonitor, [NotNullWhen(true)] out TurbineComponent? turbineComponent)
{
turbineComponent = null;
if (!_entityManager.TryGetEntity(turbineMonitor.turbine, out var turbineUid) || turbineUid == null)
return false;
if (!_entityManager.TryGetComponent<TurbineComponent>(turbineUid, out var turbine))
return false;
turbineComponent = turbine;
return true;
}
#region BUI
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > _threshold)
{
AccUpdate();
UpdateLogs();
_accumulator = 0;
}
return;
void UpdateLogs()
{
var toRemove = new List<KeyValuePair<EntityUid, EntityUid>>();
foreach (var log in _logQueue.Where(log => !((_gameTiming.RealTime - log.Value.CreationTime).TotalSeconds < 2)))
{
toRemove.Add(log.Key);
if (log.Value.SetFlowRate != null)
_adminLogger.Add(LogType.AtmosVolumeChanged, LogImpact.Medium,
$"{ToPrettyString(log.Key.Key):player} set the flow rate on {ToPrettyString(log.Value.Turbine):device} to {log.Value.SetFlowRate} through {ToPrettyString(log.Key.Value):monitor}");
if (log.Value.SetStatorLoad != null)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium,
$"{ToPrettyString(log.Key.Key):player} set the stator load on {ToPrettyString(log.Value.Turbine):device} to {log.Value.SetStatorLoad} through {ToPrettyString(log.Key.Value):monitor}");
}
foreach (var kvp in toRemove)
_logQueue.Remove(kvp);
}
}
private void AccUpdate()
{
var query = EntityQueryEnumerator<GasTurbineMonitorComponent>();
while (query.MoveNext(out var uid, out var turbineMonitor))
{
CheckRange(uid, turbineMonitor);
if (!TryGetTurbineComp(turbineMonitor, out var turbine))
continue;
_turbineSystem.UpdateUI(uid, turbine);
}
}
private void OnTurbineFlowRateChanged(EntityUid uid, GasTurbineMonitorComponent comp, TurbineChangeFlowRateMessage args)
{
if (!TryGetTurbineComp(comp, out var turbine))
return;
if(TrySetFlowRate())
{
// Data is sent to a log queue to avoid spamming the admin log when adjusting values rapidly
var key = new KeyValuePair<EntityUid, EntityUid>(args.Actor, uid);
if(!_logQueue.TryGetValue(key, out var value))
_logQueue.Add(key, new LogData
{
CreationTime = _gameTiming.RealTime,
Turbine = comp.turbine!.Value,
SetFlowRate = turbine.FlowRate
});
else
value.SetFlowRate = turbine.FlowRate;
}
_turbineSystem.UpdateUI(uid, turbine);
return;
bool TrySetFlowRate()
{
var newSet = Math.Clamp(args.FlowRate, 0f, turbine.FlowRateMax);
if (turbine.FlowRate != newSet)
{
turbine.FlowRate = newSet;
return true;
}
return false;
}
}
private void OnTurbineStatorLoadChanged(EntityUid uid, GasTurbineMonitorComponent comp, TurbineChangeStatorLoadMessage args)
{
if (!TryGetTurbineComp(comp, out var turbine))
return;
if (TrySetStatorLoad())
{
// Data is sent to a log queue to avoid spamming the admin log when adjusting values rapidly
var key = new KeyValuePair<EntityUid, EntityUid>(args.Actor, uid);
if (!_logQueue.TryGetValue(key, out var value))
_logQueue.Add(key, new LogData
{
CreationTime = _gameTiming.RealTime,
Turbine = comp.turbine!.Value,
SetStatorLoad = turbine.StatorLoad
});
else
value.SetStatorLoad = turbine.StatorLoad;
}
_turbineSystem.UpdateUI(uid, turbine);
return;
bool TrySetStatorLoad()
{
var newSet = Math.Max(args.StatorLoad, 1000f);
if (turbine.StatorLoad != newSet)
{
turbine.StatorLoad = newSet;
return true;
}
return false;
}
}
#endregion
private void OnAnchorChanged(EntityUid uid, GasTurbineMonitorComponent comp, ref AnchorStateChangedEvent args)
{
if (!args.Anchored)
return;
CheckRange(uid, comp);
}
private void CheckRange(EntityUid uid, GasTurbineMonitorComponent comp)
{
if (!_entityManager.TryGetComponent<DeviceLinkSinkComponent>(uid, out var sink) || sink.LinkedSources.Count < 1)
return;
if (!_entityManager.TryGetEntity(comp.turbine, out var uidTurbine))
return;
if (!_entityManager.TryGetComponent<DeviceLinkSourceComponent>(uidTurbine, out var source))
return;
var xformMonitor = Transform(uid);
var xformReactor = Transform(uidTurbine.Value);
var posMonitor = _transformSystem.GetWorldPosition(xformMonitor);
var posReactor = _transformSystem.GetWorldPosition(xformReactor);
if (xformMonitor.MapID == xformReactor.MapID && (posMonitor - posReactor).Length() <= source.Range)
return;
_uiSystem.CloseUi(uid, TurbineUiKey.Key);
comp.turbine = null;
_signal.RemoveSinkFromSource(uidTurbine.Value, uid, source, sink);
Dirty(uid, comp);
}
}
@@ -0,0 +1,56 @@
using Robust.Shared.Audio;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/centrifuge.dm
[RegisterComponent]
public sealed partial class NuclearCentrifugeComponent : Component
{
/// <summary>
/// Processed fuel
/// </summary>
[DataField]
public float ExtractedFuel = 0;
/// <summary>
/// Fuel left to be processed
/// </summary>
[DataField]
public float FuelToExtract = 0;
/// <summary>
/// Flag indicating the centrifuge is running
/// </summary>
[DataField]
public bool Processing = false;
/// <summary>
/// Sound played when loading an item into the centrifuge
/// </summary>
[DataField]
public SoundPathSpecifier SoundLoad = new("/Audio/Weapons/Guns/MagIn/revolver_magin.ogg");
/// <summary>
/// Sound played while the centrifuge is processing
/// </summary>
[DataField]
public SoundPathSpecifier SoundProcess = new("/Audio/Machines/spinning.ogg");
/// <summary>
/// Sound played when the centrifuge failed to create any plutonium
/// </summary>
[DataField]
public SoundPathSpecifier SoundFail = new("/Audio/Machines/buzz-two.ogg");
/// <summary>
/// Sound played when the centrifuge creates plutonium
/// </summary>
[DataField]
public SoundPathSpecifier SoundSucceed = new("/Audio/Machines/ding.ogg");
[ViewVariables]
public EntityUid? AudioProcess;
}
@@ -0,0 +1,136 @@
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Interaction;
using Content.Shared.Power;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/centrifuge.dm
public sealed class NuclearCentrifugeSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
private readonly float _threshold = 1f;
private float _accumulator = 0f;
private readonly int _stackSize = 30;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NuclearCentrifugeComponent, InteractUsingEvent>(OnInteract);
SubscribeLocalEvent<NuclearCentrifugeComponent, PowerChangedEvent>(OnPowerChange);
}
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > _threshold)
{
AccUpdate();
_accumulator = 0;
}
}
public void AccUpdate()
{
var query = EntityQueryEnumerator<NuclearCentrifugeComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if(!comp.Processing)
continue;
if(comp.FuelToExtract>0)
{
var delta = Math.Min(comp.FuelToExtract, 0.5f);
comp.ExtractedFuel += delta;
comp.FuelToExtract -= delta;
}
else
{
if(comp.ExtractedFuel > 1)
{
// If this while loop causes problems, blame whoever put 1.78e308 plutonium in the centrifuge
while (comp.ExtractedFuel > 1)
{
var plutoniumStack = Spawn("IngotPlutonium1", Transform(uid).Coordinates);
_stackSystem.SetCount(plutoniumStack, Math.Clamp((int)Math.Floor(comp.ExtractedFuel), 1, _stackSize));
comp.ExtractedFuel -= _stackSystem.GetCount(plutoniumStack);
_stackSystem.TryMergeToContacts(plutoniumStack);
}
_audio.PlayPvs(comp.SoundSucceed, uid);
}
else
{
_audio.PlayPvs(comp.SoundFail, uid, AudioParams.Default.WithVolume(-2));
}
_audio.Stop(comp.AudioProcess);
comp.Processing = false;
_appearance.SetData(uid, NuclearCentrifugeVisuals.Processing, false);
}
}
}
private void OnInteract(EntityUid uid, NuclearCentrifugeComponent comp, ref InteractUsingEvent args)
{
if (!this.IsPowered(uid, _entityManager))
return;
if (!_entityManager.TryGetComponent<ReactorPartComponent>(args.Used, out var ReactorPart) || !ReactorPart.HasRodType(ReactorPartComponent.RodTypes.FuelRod))
{
_popupSystem.PopupEntity(Loc.GetString("nuclear-centrifuge-wrong-item", ("item", args.Used)), args.User, args.User);
return;
}
if (ReactorPart.Properties == null || ReactorPart.Properties.FissileIsotopes < 0.1)
{
_popupSystem.PopupEntity(Loc.GetString("nuclear-centrifuge-unfit-item", ("item", args.Used)), args.User, args.User);
return;
}
_popupSystem.PopupEntity(Loc.GetString("nuclear-centrifuge-insert-item", ("user", args.User), ("machine", uid), ("item", args.Used)), uid);
_audio.PlayPvs(comp.SoundLoad, uid);
if(!_audio.IsPlaying(comp.AudioProcess))
comp.AudioProcess = _audio.PlayPvs(comp.SoundProcess, uid, AudioParams.Default.WithLoop(true).WithVolume(-2))?.Entity;
comp.FuelToExtract += ReactorPart.Properties.FissileIsotopes;
comp.Processing = true;
_entityManager.DeleteEntity(args.Used);
_appearance.SetData(uid, NuclearCentrifugeVisuals.Processing, true);
args.Handled = true;
}
private void OnPowerChange(EntityUid uid, NuclearCentrifugeComponent comp, ref PowerChangedEvent args)
{
if(!args.Powered && comp.Processing)
{
if(_audio.IsPlaying(comp.AudioProcess))
_audio.Stop(comp.AudioProcess);
comp.Processing = false;
}
if(args.Powered && comp.FuelToExtract > 0)
{
comp.AudioProcess = _audio.PlayPvs(comp.SoundProcess, uid, AudioParams.Default.WithLoop(true).WithVolume(-2))?.Entity;
comp.Processing = true;
}
_appearance.SetData(uid, NuclearCentrifugeVisuals.Processing, comp.Processing);
}
}
@@ -0,0 +1,195 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.DeviceLinking.Systems;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Database;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
public sealed partial class NuclearReactorMonitorSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly NuclearReactorSystem _reactorSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signal = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private readonly float _threshold = 0.5f;
private float _accumulator = 0f;
private sealed class LogData
{
public TimeSpan CreationTime;
public NetEntity Reactor;
public float? SetControlRodInsertion;
}
private readonly Dictionary<KeyValuePair<EntityUid, EntityUid>, LogData> _logQueue = [];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<NuclearReactorMonitorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<NuclearReactorMonitorComponent, NewLinkEvent>(OnNewLink);
SubscribeLocalEvent<NuclearReactorMonitorComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<NuclearReactorMonitorComponent, ReactorControlRodModifyMessage>(OnControlRodMessage);
SubscribeLocalEvent<NuclearReactorMonitorComponent, AnchorStateChangedEvent>(OnAnchorChanged);
}
private void OnMapInit(EntityUid uid, NuclearReactorMonitorComponent comp, ref MapInitEvent args)
{
if (!_entityManager.TryGetComponent<DeviceLinkSinkComponent>(uid, out var sink))
return;
foreach(var source in sink.LinkedSources)
{
if (!HasComp<NuclearReactorComponent>(source))
continue;
comp.reactor = GetNetEntity(source);
Dirty(uid, comp);
return; // The return is to make it behave such that the first connetion that's a reactor is the one chosen
}
}
private void OnNewLink(EntityUid uid, NuclearReactorMonitorComponent comp, ref NewLinkEvent args)
{
if (!HasComp<NuclearReactorComponent>(args.Source))
return;
comp.reactor = GetNetEntity(args.Source);
Dirty(uid, comp);
}
private void OnPortDisconnected(EntityUid uid, NuclearReactorMonitorComponent comp, ref PortDisconnectedEvent args)
{
if (args.Port != comp.LinkingPort)
return;
comp.reactor = null;
Dirty(uid, comp);
}
public bool TryGetReactorComp(NuclearReactorMonitorComponent reactorMonitor, [NotNullWhen(true)] out NuclearReactorComponent? reactorComponent)
{
reactorComponent = null;
if (!_entityManager.TryGetEntity(reactorMonitor.reactor, out var reactorEnt) || reactorEnt == null)
return false;
if (!_entityManager.TryGetComponent<NuclearReactorComponent>(reactorEnt, out var reactor))
return false;
reactorComponent = reactor;
return true;
}
#region BUI
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > _threshold)
{
AccUpdate();
UpdateLogs();
_accumulator = 0;
}
return;
void UpdateLogs()
{
var toRemove = new List<KeyValuePair<EntityUid, EntityUid>>();
foreach (var log in _logQueue.Where(log => !((_gameTiming.RealTime - log.Value.CreationTime).TotalSeconds < 2)))
{
toRemove.Add(log.Key);
if (log.Value.SetControlRodInsertion != null)
_adminLog.Add(LogType.Action, $"{ToPrettyString(log.Key.Key):actor} set control rod insertion of {ToPrettyString(log.Value.Reactor):target} to {log.Value.SetControlRodInsertion} through {ToPrettyString(log.Key.Value):monitor}");
}
foreach (var kvp in toRemove)
_logQueue.Remove(kvp);
}
}
private void AccUpdate()
{
var query = EntityQueryEnumerator<NuclearReactorMonitorComponent>();
while (query.MoveNext(out var uid, out var reactorMonitor))
{
CheckRange(uid, reactorMonitor);
if (!TryGetReactorComp(reactorMonitor, out var reactor))
continue;
_reactorSystem.UpdateUI(uid, reactor);
}
}
private void OnControlRodMessage(EntityUid uid, NuclearReactorMonitorComponent comp, ref ReactorControlRodModifyMessage args)
{
if (!TryGetReactorComp(comp, out var reactor))
return;
if(SharedNuclearReactorSystem.AdjustControlRods(reactor, args.Change))
{
// Data is sent to a log queue to avoid spamming the admin log when adjusting values rapidly
var key = new KeyValuePair<EntityUid, EntityUid>(args.Actor, uid);
if(!_logQueue.TryGetValue(key, out var value))
_logQueue.Add(key, new LogData {
CreationTime = _gameTiming.RealTime,
Reactor = comp.reactor!.Value,
SetControlRodInsertion = reactor.ControlRodInsertion
});
else
value.SetControlRodInsertion = reactor.ControlRodInsertion;
}
_reactorSystem.UpdateUI(uid, reactor);
}
#endregion
private void OnAnchorChanged(EntityUid uid, NuclearReactorMonitorComponent comp, ref AnchorStateChangedEvent args)
{
if (!args.Anchored)
return;
CheckRange(uid, comp);
}
private void CheckRange(EntityUid uid, NuclearReactorMonitorComponent comp)
{
if (!_entityManager.TryGetComponent<DeviceLinkSinkComponent>(uid, out var sink) || sink.LinkedSources.Count < 1)
return;
if (!_entityManager.TryGetEntity(comp.reactor, out var uidReactor))
return;
if (!_entityManager.TryGetComponent<DeviceLinkSourceComponent>(uidReactor, out var source))
return;
var xformMonitor = Transform(uid);
var xformReactor = Transform(uidReactor.Value);
var posMonitor = _transformSystem.GetWorldPosition(xformMonitor);
var posReactor = _transformSystem.GetWorldPosition(xformReactor);
if (xformMonitor.MapID == xformReactor.MapID && (posMonitor - posReactor).Length() <= source.Range)
return;
_uiSystem.CloseUi(uid, NuclearReactorUiKey.Key);
comp.reactor = null;
_signal.RemoveSinkFromSource(uidReactor.Value, uid, source, sink);
Dirty(uid, comp);
}
}
@@ -0,0 +1,614 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Atmos;
using Robust.Shared.Random;
using Content.Shared._FarHorizons.Materials.Systems;
using Content.Shared.Examine;
using Content.Shared.Nutrition;
using Content.Shared.Radiation.Components;
using Content.Shared.Damage.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/reactorcomponents.dm
public sealed class ReactorPartSystem : SharedReactorPartSystem
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly SharedPointLightSystem _lightSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
/// <summary>
/// Changes the overall rate of events
/// </summary>
private readonly float _rate = 5;
/// <summary>
/// Changes the likelyhood of neutron interactions
/// </summary>
private readonly float _bias = 1.5f;
/// <summary>
/// The amount of a property consumed by a reaction
/// </summary>
private readonly float _reactant = 0.01f;
/// <summary>
/// The amount of a property resultant from a reaction
/// </summary>
private readonly float _product = 0.005f;
/// <summary>
/// Temperature (in C) when people's hands can be burnt
/// </summary>
private readonly static float _hotTemp = 80;
/// <summary>
/// Temperature (in C) when insulated gloves can no longer protect
/// </summary>
private readonly static float _burnTemp = 400;
private readonly static float _burnDiv = (_burnTemp - _hotTemp) / 5; // The 5 is how much heat damage insulated gloves protect from
private readonly float _threshold = 1f;
private float _accumulator = 0f;
#region Item Methods
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReactorPartComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<ReactorPartComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ReactorPartComponent, IngestedEvent>(OnIngest);
}
private void OnInit(EntityUid uid, ReactorPartComponent component, ref MapInitEvent args)
{
var radvalue = (component.Properties.Radioactivity * 0.1f) + (component.Properties.NeutronRadioactivity * 0.15f) + (component.Properties.FissileIsotopes * 0.125f);
if (radvalue > 0)
{
var radcomp = EnsureComp<RadiationSourceComponent>(uid);
radcomp.Intensity = radvalue;
}
if (component.Properties.NeutronRadioactivity > 0)
{
var lightcomp = _lightSystem.EnsureLight(uid);
_lightSystem.SetEnergy(uid, component.Properties.NeutronRadioactivity, lightcomp);
_lightSystem.SetColor(uid, Color.FromHex("#22bbff"), lightcomp);
_lightSystem.SetRadius(uid, 1.2f, lightcomp);
}
}
private void OnExamine(Entity<ReactorPartComponent> ent, ref ExaminedEvent args)
{
var comp = ent.Comp;
if (!args.IsInDetailsRange)
return;
using (args.PushGroup(nameof(ReactorPartComponent)))
{
switch (comp.Properties.NeutronRadioactivity)
{
case > 8:
args.PushMarkup(Loc.GetString("reactor-part-nrad-5"));
break;
case > 6:
args.PushMarkup(Loc.GetString("reactor-part-nrad-4"));
break;
case > 4:
args.PushMarkup(Loc.GetString("reactor-part-nrad-3"));
break;
case > 2:
args.PushMarkup(Loc.GetString("reactor-part-nrad-2"));
break;
case > 1:
args.PushMarkup(Loc.GetString("reactor-part-nrad-1"));
break;
case > 0:
args.PushMarkup(Loc.GetString("reactor-part-nrad-0"));
break;
}
switch (comp.Properties.Radioactivity)
{
case > 8:
args.PushMarkup(Loc.GetString("reactor-part-rad-5"));
break;
case > 6:
args.PushMarkup(Loc.GetString("reactor-part-rad-4"));
break;
case > 4:
args.PushMarkup(Loc.GetString("reactor-part-rad-3"));
break;
case > 2:
args.PushMarkup(Loc.GetString("reactor-part-rad-2"));
break;
case > 1:
args.PushMarkup(Loc.GetString("reactor-part-rad-1"));
break;
case > 0:
args.PushMarkup(Loc.GetString("reactor-part-rad-0"));
break;
}
if (comp.Temperature > Atmospherics.T0C + _burnTemp)
args.PushMarkup(Loc.GetString("reactor-part-burning"));
else if (comp.Temperature > Atmospherics.T0C + _hotTemp)
args.PushMarkup(Loc.GetString("reactor-part-hot"));
}
}
private void OnIngest(Entity<ReactorPartComponent> ent, ref IngestedEvent args)
{
var comp = ent.Comp;
if (comp.Properties == null)
return;
var properties = comp.Properties;
if (!_entityManager.TryGetComponent<DamageableComponent>(args.Target, out var damageable) || damageable.Damage.DamageDict == null)
return;
var dict = damageable.Damage.DamageDict;
var dmgKey = "Radiation";
var dmg = (properties.NeutronRadioactivity * 20) + (properties.Radioactivity * 10) + (properties.FissileIsotopes * 5);
if (!dict.TryAdd(dmgKey, dmg))
{
var prev = dict[dmgKey];
dict.Remove(dmgKey);
dict.Add(dmgKey, prev + dmg);
}
}
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > _threshold)
{
AccUpdate();
_accumulator = 0;
}
}
private void AccUpdate()
{
var query = EntityQueryEnumerator<ReactorPartComponent>();
while (query.MoveNext(out var uid, out var component))
{
var gasMix = _atmosphereSystem.GetTileMixture(uid, true) ?? GasMixture.SpaceGas;
var DeltaT = (component.Temperature - gasMix.Temperature) * 0.01f;
if (Math.Abs(DeltaT) < 0.1)
continue;
// This viloates the laws of physics, but if energy is conserved, then pulling out a hot rod will turn the room into an oven
// Also does not take into account thermal mass
component.Temperature -= DeltaT;
if (!gasMix.Immutable) // This prevents it from heating up space itself
gasMix.Temperature += DeltaT;
var burncomp = EnsureComp<DamageOnInteractComponent>(uid);
burncomp.IsDamageActive = component.Temperature > Atmospherics.T0C + _hotTemp;
if (burncomp.IsDamageActive)
{
var damage = Math.Max((component.Temperature - Atmospherics.T0C - _hotTemp) / _burnDiv, 0);
// Giant string of if/else that makes sure it will interfere only as much as it needs to
if (burncomp.Damage == null)
burncomp.Damage = new() { DamageDict = new() { { "Heat", damage } } };
else if (burncomp.Damage.DamageDict == null)
burncomp.Damage.DamageDict = new() { { "Heat", damage } };
else if (!burncomp.Damage.DamageDict.ContainsKey("Heat"))
burncomp.Damage.DamageDict.Add("Heat", damage);
else
burncomp.Damage.DamageDict["Heat"] = damage;
}
Dirty(uid, burncomp);
}
}
#endregion
#region Reactor Methods
/// <summary>
/// Processes gas flowing through a reactor part.
/// </summary>
/// <param name="reactorPart">The reactor part.</param>
/// <param name="reactorEnt">The entity representing the reactor this part is inserted into.</param>
/// <param name="inGas">The gas to be processed.</param>
/// <returns></returns>
public GasMixture? ProcessGas(ReactorPartComponent reactorPart, Entity<NuclearReactorComponent> reactorEnt, GasMixture inGas)
{
if (!reactorPart.HasRodType(ReactorPartComponent.RodTypes.GasChannel))
return null;
GasMixture? ProcessedGas = null;
if (reactorPart.AirContents != null)
{
var compTemp = reactorPart.Temperature;
var gasTemp = reactorPart.AirContents.Temperature;
var DeltaT = compTemp - gasTemp;
var DeltaTr = (compTemp + gasTemp) * (compTemp - gasTemp) * (Math.Pow(compTemp, 2) + Math.Pow(gasTemp, 2));
var k = MaterialSystem.CalculateHeatTransferCoefficient(reactorPart.Properties, null);
var A = reactorPart.GasThermalCrossSection * (0.4 * 8);
var ThermalEnergy = _atmosphereSystem.GetThermalEnergy(reactorPart.AirContents);
var Hottest = Math.Max(gasTemp, compTemp);
var Coldest = Math.Min(gasTemp, compTemp);
var MaxDeltaE = Math.Clamp((k * A * DeltaT) + (5.67037442e-8 * A * DeltaTr),
(compTemp * reactorPart.ThermalMass) - (Hottest * reactorPart.ThermalMass),
(compTemp * reactorPart.ThermalMass) - (Coldest * reactorPart.ThermalMass));
reactorPart.AirContents.Temperature = (float)Math.Clamp(gasTemp +
(MaxDeltaE / _atmosphereSystem.GetHeatCapacity(reactorPart.AirContents, true)), Coldest, Hottest);
reactorPart.Temperature = (float)Math.Clamp(compTemp -
((_atmosphereSystem.GetThermalEnergy(reactorPart.AirContents) - ThermalEnergy) / reactorPart.ThermalMass), Coldest, Hottest);
if (gasTemp < 0 || compTemp < 0)
throw new Exception("Reactor part temperature went below 0k.");
if (reactorPart.Melted)
{
var T = _atmosphereSystem.GetTileMixture(reactorEnt.Owner, excite: true);
if (T != null)
_atmosphereSystem.Merge(T, reactorPart.AirContents);
}
else
ProcessedGas = reactorPart.AirContents;
}
if (inGas != null && _atmosphereSystem.GetThermalEnergy(inGas) > 0)
{
reactorPart.AirContents = inGas.RemoveVolume(reactorPart.GasVolume);
reactorPart.AirContents.Volume = reactorPart.GasVolume;
if (reactorPart.AirContents != null && reactorPart.AirContents.TotalMoles < 1)
{
if (ProcessedGas != null)
{
_atmosphereSystem.Merge(ProcessedGas, reactorPart.AirContents);
reactorPart.AirContents.Clear();
}
else
{
ProcessedGas = reactorPart.AirContents;
reactorPart.AirContents.Clear();
}
}
}
return ProcessedGas;
}
/// <summary>
/// Processes heat transfer within the reactor grid.
/// </summary>
/// <param name="reactorPart">Reactor part applying the calculations.</param>
/// <param name="reactorEnt">Reactor housing the reactor part.</param>
/// <param name="AdjacentComponents">List of reactor parts next to the reactorPart.</param>
/// <param name="reactorSystem">The SharedNuclearReactorSystem.</param>
/// <exception cref="Exception">Calculations resulted in a sub-zero value.</exception>
public void ProcessHeat(ReactorPartComponent reactorPart, Entity<NuclearReactorComponent> reactorEnt, List<ReactorPartComponent?> AdjacentComponents, SharedNuclearReactorSystem reactorSystem)
{
var reactor = reactorEnt.Comp;
// Intercomponent calculation
foreach (var RC in AdjacentComponents)
{
if (RC == null)
continue;
var DeltaT = reactorPart.Temperature - RC.Temperature;
var k = MaterialSystem.CalculateHeatTransferCoefficient(reactorPart.Properties, RC.Properties);
var A = Math.Min(reactorPart.ThermalCrossSection, RC.ThermalCrossSection);
reactorPart.Temperature = (float)(reactorPart.Temperature - (k * A * (0.5 * 8) / reactorPart.ThermalMass * DeltaT));
RC.Temperature = (float)(RC.Temperature - (k * A * (0.5 * 8) / RC.ThermalMass * -DeltaT));
if (RC.Temperature < 0 || reactorPart.Temperature < 0)
throw new Exception("ReactorPart-ReactorPart temperature calculation resulted in sub-zero value.");
ProcessHeatEffects(RC);
ProcessHeatEffects(reactorPart);
}
// Component-Reactor calculation
if (reactor != null)
{
var DeltaT = reactorPart.Temperature - reactor.Temperature;
var k = MaterialSystem.CalculateHeatTransferCoefficient(reactorPart.Properties, _proto.Index(reactor.Material).Properties);
var A = reactorPart.ThermalCrossSection;
reactorPart.Temperature = (float)(reactorPart.Temperature - (k * A * (0.5 * 8) / reactorPart.ThermalMass * DeltaT));
reactor.Temperature = (float)(reactor.Temperature - (k * A * (0.5 * 8) / reactor.ThermalMass * -DeltaT));
if (reactor.Temperature < 0 || reactorPart.Temperature < 0)
throw new Exception("Reactor-ReactorPart temperature calculation resulted in sub-zero value.");
ProcessHeatEffects(reactorPart);
}
if (reactorPart.Temperature > reactorPart.MeltingPoint && reactorPart.MeltHealth > 0)
reactorPart.MeltHealth -= _random.Next(10, 50 + 1);
if (reactorPart.MeltHealth <= 0)
Melt(reactorPart, reactorEnt, reactorSystem);
return;
// I would really like for these to be defined by the MaterialPrototype, like GasReactionPrototype, but it caused the client and server to fight when I tried
// Also, function in a function because I found it funny
void ProcessHeatEffects(ReactorPartComponent part)
{
switch (part.Material)
{
case "Plasma":
PlasmaTemperatureEffects(part);
break;
default:
break;
}
}
void PlasmaTemperatureEffects(ReactorPartComponent part)
{
var temperatureThreshold = Atmospherics.T0C + 80;
if (part.Temperature <= temperatureThreshold || part.Properties.ActivePlasma <= 0)
return;
var molesPerUnit = 100f; // Arbitrary value for how much gaseous plasma is in each unit of active plasma
var payload = new GasMixture();
payload.SetMoles(Gas.Plasma, (float)Math.Min(part.Properties.ActivePlasma * molesPerUnit, Math.Log(((part.Temperature - temperatureThreshold) / 100) + 1)));
payload.Temperature = part.Temperature;
part.Properties.ActivePlasma -= payload.GetMoles(Gas.Plasma) / molesPerUnit;
reactor.AirContents ??= new GasMixture();
_atmosphereSystem.Merge(reactor.AirContents, payload);
}
}
/// <summary>
/// Melts the related ReactorPart.
/// </summary>
/// <param name="reactorPart">Reactor part to be melted</param>
/// <param name="reactorEnt">Reactor housing the reactor part</param>
/// <param name="reactorSystem">The SharedNuclearReactorSystem</param>
public void Melt(ReactorPartComponent reactorPart, Entity<NuclearReactorComponent> reactorEnt, SharedNuclearReactorSystem reactorSystem)
{
if (reactorPart.Melted)
return;
reactorPart.Melted = true;
reactorPart.IconStateCap += "_melted_" + _random.Next(1, 4 + 1);
reactorSystem.UpdateGridVisual(reactorEnt);
reactorPart.NeutronCrossSection = 5f;
reactorPart.ThermalCrossSection = 20f;
reactorPart.IsControlRod = false;
if(reactorPart.HasRodType(ReactorPartComponent.RodTypes.GasChannel))
reactorPart.GasThermalCrossSection = 0.1f;
}
/// <summary>
/// Returns a list of neutrons from the interation of the given ReactorPart and initial neutrons.
/// </summary>
/// <param name="reactorPart">Reactor part applying the calculations.</param>
/// <param name="neutrons">List of neutrons to be processed.</param>
/// <param name="thermalEnergy">Thermal energy released from the process.</param>
/// <returns>Post-processing list of neutrons.</returns>
public List<ReactorNeutron> ProcessNeutrons(ReactorPartComponent reactorPart, List<ReactorNeutron> neutrons, out float thermalEnergy)
{
var preCalcTemp = reactorPart.Temperature;
var flux = new List<ReactorNeutron>(neutrons);
foreach (var neutron in flux)
{
if (Prob(reactorPart.Properties.Density * _rate * reactorPart.NeutronCrossSection * _bias))
{
if (neutron.velocity <= 1 && Prob(_rate * reactorPart.Properties.NeutronRadioactivity * _bias)) // neutron stimulated emission
{
reactorPart.Properties.NeutronRadioactivity -= _reactant;
reactorPart.Properties.Radioactivity += _product;
for (var i = 0; i < _random.Next(3, 5 + 1); i++) // was 1, 5+1
{
neutrons.Add(new() { dir = _random.NextAngle().GetDir(), velocity = _random.Next(2, 3 + 1) });
}
neutrons.Remove(neutron);
reactorPart.Temperature += 75f; // Was 50, increased to make neutron reactions stronger
}
else if (neutron.velocity <= 5 && Prob(_rate * reactorPart.Properties.Radioactivity * _bias)) // stimulated emission
{
reactorPart.Properties.Radioactivity -= _reactant;
reactorPart.Properties.FissileIsotopes += _product;
for (var i = 0; i < _random.Next(3, 5 + 1); i++)// was 1, 5+1
{
neutrons.Add(new() { dir = _random.NextAngle().GetDir(), velocity = _random.Next(1, 3 + 1) });
}
neutrons.Remove(neutron);
reactorPart.Temperature += 50f; // Was 25, increased to make neutron reactions stronger
}
else
{
if (Prob(_rate * reactorPart.Properties.Hardness)) // reflection, based on hardness
// A really complicated way of saying do a 180 or a 180+/-45
neutron.dir = (neutron.dir.GetOpposite().ToAngle() + (_random.NextAngle() / 4) - (MathF.Tau / 8)).GetDir();
else if (reactorPart.IsControlRod)
neutron.velocity = 0;
else
neutron.velocity--;
if (neutron.velocity <= 0)
neutrons.Remove(neutron);
reactorPart.Temperature += 1; // ... not worth the adjustment
}
}
}
if (Prob(reactorPart.Properties.NeutronRadioactivity * _rate * reactorPart.NeutronCrossSection))
{
var count = _random.Next(1, 5 + 1); // Was 3+1
for (var i = 0; i < count; i++)
{
neutrons.Add(new() { dir = _random.NextAngle().GetDir(), velocity = 3 });
}
reactorPart.Properties.NeutronRadioactivity -= _reactant / 2;
reactorPart.Properties.Radioactivity += _product / 2;
//This code has been deactivated so neutrons would have a bigger impact
//reactorPart.Temperature += 13; // 20 * 0.65
}
if (Prob(reactorPart.Properties.Radioactivity * _rate * reactorPart.NeutronCrossSection))
{
var count = _random.Next(1, 5 + 1); // Was 3+1
for (var i = 0; i < count; i++)
{
neutrons.Add(new() { dir = _random.NextAngle().GetDir(), velocity = _random.Next(1, 3 + 1) });
}
reactorPart.Properties.Radioactivity -= _reactant / 2;
reactorPart.Properties.FissileIsotopes += _product / 2;
//This code has been deactivated so neutrons would have a bigger impact
//reactorPart.Temperature += 6.5f; // 10 * 0.65
}
if (reactorPart.HasRodType(ReactorPartComponent.RodTypes.ControlRod))
{
if (!reactorPart.Melted && (reactorPart.NeutronCrossSection != reactorPart.ConfiguredInsertionLevel))
{
if (reactorPart.ConfiguredInsertionLevel < reactorPart.NeutronCrossSection)
reactorPart.NeutronCrossSection -= Math.Min(0.1f, reactorPart.NeutronCrossSection - reactorPart.ConfiguredInsertionLevel);
else
reactorPart.NeutronCrossSection += Math.Min(0.1f, reactorPart.ConfiguredInsertionLevel - reactorPart.NeutronCrossSection);
}
}
if (reactorPart.HasRodType(ReactorPartComponent.RodTypes.GasChannel))
neutrons = ProcessNeutronsGas(reactorPart, neutrons);
neutrons ??= [];
thermalEnergy = (reactorPart.Temperature - preCalcTemp) * reactorPart.ThermalMass;
return neutrons;
}
/// <summary>
/// Processes neutrons interacting with gas in a reactor part.
/// </summary>
/// <param name="reactorPart">The reactor part to process neutrons for.</param>
/// <param name="neutrons">The list of neutrons to process.</param>
/// <returns>The updated list of neutrons after processing.</returns>
private List<ReactorNeutron> ProcessNeutronsGas(ReactorPartComponent reactorPart, List<ReactorNeutron> neutrons)
{
if (reactorPart.AirContents == null) return neutrons;
var flux = new List<ReactorNeutron>(neutrons);
foreach (var neutron in flux)
{
if (neutron.velocity > 0)
{
var neutronCount = GasNeutronInteract(reactorPart);
if (neutronCount > 1)
for (var i = 0; i < neutronCount; i++)
neutrons.Add(new() { dir = _random.NextAngle().GetDir(), velocity = _random.Next(1, 3 + 1) });
else if (neutronCount < 1)
neutrons.Remove(neutron);
}
}
return neutrons;
}
/// <summary>
/// Performs neutron interactions with the gas in the reactor part.
/// </summary>
/// <param name="reactorPart">The reactor part to process neutron interactions for.</param>
/// <returns>Change in number of neutrons.</returns>
private int GasNeutronInteract(ReactorPartComponent reactorPart)
{
if (reactorPart.AirContents == null)
return 1;
var neutronCount = 1;
var gas = reactorPart.AirContents;
if (gas.GetMoles(Gas.Plasma) > 1)
{
var reactMolPerLiter = 0.25;
var reactMol = reactMolPerLiter * gas.Volume;
var plasma = gas.GetMoles(Gas.Plasma);
var plasmaReactCount = (int)Math.Round((plasma - (plasma % reactMol)) / reactMol) + (Prob(plasma - (plasma % reactMol)) ? 1 : 0);
plasmaReactCount = _random.Next(0, plasmaReactCount + 1);
gas.AdjustMoles(Gas.Plasma, plasmaReactCount * -0.5f);
gas.AdjustMoles(Gas.Tritium, plasmaReactCount * 2);
neutronCount += plasmaReactCount;
}
if (gas.GetMoles(Gas.CarbonDioxide) > 1)
{
var reactMolPerLiter = 0.4;
var reactMol = reactMolPerLiter * gas.Volume;
var co2 = gas.GetMoles(Gas.CarbonDioxide);
var co2ReactCount = (int)Math.Round((co2 - (co2 % reactMol)) / reactMol) + (Prob(co2 - (co2 % reactMol)) ? 1 : 0);
co2ReactCount = _random.Next(0, co2ReactCount + 1);
reactorPart.Temperature += Math.Min(co2ReactCount, neutronCount);
neutronCount -= Math.Min(co2ReactCount, neutronCount);
}
if (gas.GetMoles(Gas.Tritium) > 1)
{
var reactMolPerLiter = 0.5;
var reactMol = reactMolPerLiter * gas.Volume;
var tritium = gas.GetMoles(Gas.Tritium);
var tritiumReactCount = (int)Math.Round((tritium - (tritium % reactMol)) / reactMol) + (Prob(tritium - (tritium % reactMol)) ? 1 : 0);
tritiumReactCount = _random.Next(0, tritiumReactCount + 1);
if (tritiumReactCount > 0)
{
gas.AdjustMoles(Gas.Tritium, -1 * tritiumReactCount);
reactorPart.Temperature += 1 * tritiumReactCount;
switch (_random.Next(0, 5))
{
case 0:
gas.AdjustMoles(Gas.Oxygen, 0.5f * tritiumReactCount);
break;
case 1:
gas.AdjustMoles(Gas.Nitrogen, 0.5f * tritiumReactCount);
break;
case 2:
gas.AdjustMoles(Gas.Ammonia, 0.1f * tritiumReactCount);
break;
case 3:
gas.AdjustMoles(Gas.NitrousOxide, 0.1f * tritiumReactCount);
break;
case 4:
gas.AdjustMoles(Gas.Frezon, 0.1f * tritiumReactCount);
break;
default:
break;
}
}
}
return neutronCount;
}
/// <summary>
/// Probablity check that accepts chances > 100%
/// </summary>
/// <param name="chance">The chance percentage between 0 and 100.</param>
private bool Prob(double chance) => _random.NextDouble() <= chance / 100;
#endregion
}
@@ -0,0 +1,679 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Explosion.EntitySystems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
using Content.Shared.Database;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Random;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Shared.Construction.Components;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceNetwork;
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Damage.Systems;
using Content.Server.Audio;
using Content.Shared.Audio;
using Robust.Shared.Timing;
using System.Linq;
namespace Content.Server._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/turbine.dm
public sealed class TurbineSystem : SharedTurbineSystem
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly GunSystem _gun = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
[Dependency] private readonly DeviceLinkSystem _signal = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private readonly List<string> _damageSoundList = [
"/Audio/_FarHorizons/Effects/engine_grump1.ogg",
"/Audio/_FarHorizons/Effects/engine_grump2.ogg",
"/Audio/_FarHorizons/Effects/engine_grump3.ogg",
"/Audio/Effects/metal_slam5.ogg",
"/Audio/Effects/metal_scrape2.ogg"
];
private sealed class LogData
{
public TimeSpan CreationTime;
public float? SetFlowRate;
public float? SetStatorLoad;
}
private readonly Dictionary<KeyValuePair<EntityUid, EntityUid>, LogData> _logQueue = [];
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TurbineComponent, MapInitEvent>(OnInit);
SubscribeLocalEvent<TurbineComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<TurbineComponent, DamageChangedEvent>(OnDamaged);
SubscribeLocalEvent<TurbineComponent, ItemSlotInsertAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<TurbineComponent, ItemSlotEjectAttemptEvent>(OnEjectAttempt);
SubscribeLocalEvent<TurbineComponent, EntInsertedIntoContainerMessage>(OnPartInserted);
SubscribeLocalEvent<TurbineComponent, EntRemovedFromContainerMessage>(OnPartEjected);
SubscribeLocalEvent<TurbineComponent, AtmosDeviceUpdateEvent>(OnUpdate);
SubscribeLocalEvent<TurbineComponent, GasAnalyzerScanEvent>(OnAnalyze);
SubscribeLocalEvent<TurbineComponent, TurbineChangeFlowRateMessage>(OnTurbineFlowRateChanged);
SubscribeLocalEvent<TurbineComponent, TurbineChangeStatorLoadMessage>(OnTurbineStatorLoadChanged);
SubscribeLocalEvent<TurbineComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<TurbineComponent, PortDisconnectedEvent>(OnPortDisconnected);
SubscribeLocalEvent<TurbineComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<TurbineComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
}
private const string BladeContainer = "blade_slot";
private const string StatorContainer = "stator_slot";
private void OnInit(EntityUid uid, TurbineComponent comp, ref MapInitEvent args)
{
_signal.EnsureSourcePorts(uid, comp.SpeedHighPort, comp.SpeedLowPort, comp.TurbineDataPort);
_signal.EnsureSinkPorts(uid, comp.StatorLoadIncreasePort, comp.StatorLoadDecreasePort);
TryGetPart(uid, BladeContainer, out comp.CurrentBlade);
TryGetPart(uid, StatorContainer, out comp.CurrentStator);
UpdatePartValues(comp);
comp.AlarmAudioOvertemp = SpawnAttachedTo("GasTurbineAlarmEntity", new(uid, 0, 0));
comp.AlarmAudioUnderspeed = SpawnAttachedTo("GasTurbineAlarmEntity", new(uid, 0, 0));
_ambientSoundSystem.SetSound(comp.AlarmAudioUnderspeed.Value, new SoundPathSpecifier("/Audio/_FarHorizons/Machines/alarm_beep.ogg"));
_ambientSoundSystem.SetVolume(comp.AlarmAudioUnderspeed.Value, -4);
}
private bool TryGetPart(EntityUid uid, string slot, [NotNullWhen(true)] out EntityUid? part)
{
part = null;
if (!_containerSystem.TryGetContainer(uid, slot, out var container) || container.ContainedEntities.Count == 0)
return false;
part = container.ContainedEntities[0];
return true;
}
private void OnAnalyze(EntityUid uid, TurbineComponent comp, ref GasAnalyzerScanEvent args)
{
if (!comp.InletEnt.HasValue || !comp.OutletEnt.HasValue)
return;
args.GasMixtures ??= [];
if (_nodeContainer.TryGetNode(comp.InletEnt.Value, comp.PipeName, out PipeNode? inlet) && inlet.Air.Volume != 0f)
{
var inletAirLocal = inlet.Air.Clone();
inletAirLocal.Multiply(inlet.Volume / inlet.Air.Volume);
inletAirLocal.Volume = inlet.Volume;
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-inlet"), inletAirLocal));
}
if (_nodeContainer.TryGetNode(comp.OutletEnt.Value, comp.PipeName, out PipeNode? outlet) && outlet.Air.Volume != 0f)
{
var outletAirLocal = outlet.Air.Clone();
outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume);
outletAirLocal.Volume = outlet.Volume;
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal));
}
}
private void OnShutdown(EntityUid uid, TurbineComponent comp, ref ComponentShutdown args)
{
QueueDel(comp.InletEnt);
QueueDel(comp.OutletEnt);
}
#region Main Loop
private void OnUpdate(EntityUid uid, TurbineComponent comp, ref AtmosDeviceUpdateEvent args)
{
var supplier = Comp<PowerSupplierComponent>(uid);
comp.SupplierMaxSupply = supplier.MaxSupply;
comp.SupplierLastSupply = supplier.CurrentSupply;
supplier.MaxSupply = comp.LastGen;
if(!GetPipes(uid, comp, out var inlet, out var outlet))
return;
if (comp.CurrentBlade == null || comp.CurrentStator == null)
comp.Ruined = true;
UpdateAppearance(uid, comp);
var transferVolume = CalculateTransferVolume(comp, inlet, outlet, args.dt);
var AirContents = inlet.Air.RemoveVolume(transferVolume) ?? new GasMixture();
comp.LastVolumeTransfer = transferVolume;
comp.LastGen = 0;
comp.Overtemp = AirContents.Temperature >= comp.MaxTemp - 500;
comp.Undertemp = AirContents.Temperature <= comp.MinTemp;
// Dump gas into atmosphere
if (comp.Ruined || AirContents.Temperature >= comp.MaxTemp)
{
var tile = _atmosphereSystem.GetTileMixture(uid, excite: true);
if (tile != null)
{
_atmosphereSystem.Merge(tile, AirContents);
}
// This does rely on the alarm existing, but if it doesn't then there are bigger problems
if (!comp.Ruined && _entityManager.TryGetComponent<AmbientSoundComponent>(comp.AlarmAudioOvertemp, out var ambience) && !ambience.Enabled)
_popupSystem.PopupEntity(Loc.GetString("turbine-overheat", ("owner", uid)), uid, PopupType.LargeCaution);
// Prevent power from being generated by residual gasses
AirContents.Clear();
}
if(Exists(comp.AlarmAudioOvertemp))
_ambientSoundSystem.SetAmbience(comp.AlarmAudioOvertemp.Value, !comp.Ruined && AirContents.Temperature >= comp.MaxTemp);
// Update stator load based on device network
if (comp.IncreasePortState != SignalState.Low)
AdjustStatorLoad(comp, 1000);
if (comp.DecreasePortState != SignalState.Low)
AdjustStatorLoad(comp, -1000);
if (comp.IncreasePortState == SignalState.Momentary)
comp.IncreasePortState = SignalState.Low;
if (comp.DecreasePortState == SignalState.Momentary)
comp.DecreasePortState = SignalState.Low;
if (!comp.Ruined && AirContents != null)
{
var InputStartingEnergy = _atmosphereSystem.GetThermalEnergy(AirContents);
var InputHeatCap = _atmosphereSystem.GetHeatCapacity(AirContents, true);
// Prevents div by 0 if it would come up
if (InputStartingEnergy <= 0)
{
InputStartingEnergy = 1;
}
if (InputHeatCap <= 0)
{
InputHeatCap = 1;
}
if (AirContents.Temperature > comp.MinTemp)
{
AirContents.Temperature = (float)Math.Max((InputStartingEnergy - ((InputStartingEnergy - (InputHeatCap * Atmospherics.T20C)) * 0.8)) / InputHeatCap, Atmospherics.T20C);
}
var OutputStartingEnergy = _atmosphereSystem.GetThermalEnergy(AirContents);
var EnergyGenerated = comp.StatorLoad * (comp.RPM / 60);
var DeltaE = InputStartingEnergy - OutputStartingEnergy;
float NewRPM;
if (DeltaE - EnergyGenerated > 0)
{
NewRPM = comp.RPM + (float)Math.Sqrt(2 * (Math.Max(DeltaE - EnergyGenerated, 0) / comp.TurbineMass));
}
else
{
NewRPM = comp.RPM - (float)Math.Sqrt(2 * (Math.Max(EnergyGenerated - DeltaE, 0) / comp.TurbineMass));
}
var NextGen = comp.StatorLoad * (Math.Max(NewRPM, 0) / 60);
float NextRPM;
if (DeltaE - NextGen > 0)
{
NextRPM = comp.RPM + (float)Math.Sqrt(2 * (Math.Max(DeltaE - NextGen, 0) / comp.TurbineMass));
}
else
{
NextRPM = comp.RPM - (float)Math.Sqrt(2 * (Math.Max(NextGen - DeltaE, 0) / comp.TurbineMass));
}
if (NewRPM < 0 || NextRPM < 0)
{
// Stator load is too high
comp.Stalling = true;
comp.RPM = 0;
}
else
{
comp.Stalling = false;
comp.RPM = NextRPM;
}
if(Exists(comp.AlarmAudioUnderspeed))
_ambientSoundSystem.SetAmbience(comp.AlarmAudioUnderspeed.Value, !comp.Ruined && comp.Stalling && !comp.Undertemp && comp.FlowRate > 0);
if (comp.RPM > 10)
{
// Sacrifices must be made to have a smooth ramp up:
// This will generate 2 audio streams every second with up to 4 of them playing at once... surely this can't go wrong :clueless:
_audio.PlayPvs(new SoundPathSpecifier("/Audio/_FarHorizons/Ambience/Objects/turbine_room.ogg"), uid, AudioParams.Default.WithPitchScale(comp.RPM / comp.BestRPM).WithVolume(-2));
}
// Calculate power generation
comp.LastGen = comp.PowerMultiplier * comp.StatorLoad * (comp.RPM / 30) * (float)(1 / Math.Cosh(0.01 * (comp.RPM - comp.BestRPM)));
if (float.IsNaN(comp.LastGen))
throw new NotFiniteNumberException("Turbine made NaN power");
comp.Overspeed = comp.RPM > comp.BestRPM * 1.2;
// Damage the turbines during overspeed, linear increase from 18% to 45% then stays at 45%
if (comp.Overspeed && _random.NextFloat() < 0.15 * Math.Min(comp.RPM / comp.BestRPM, 3))
{
// TODO: damage flash
_audio.PlayPvs(new SoundPathSpecifier(_damageSoundList[_random.Next(0, _damageSoundList.Count - 1)]), uid, AudioParams.Default.WithVariation(0.25f).WithVolume(-1));
comp.BladeHealth--;
UpdateHealthIndicators(uid, comp);
}
_atmosphereSystem.Merge(outlet.Air, AirContents);
}
// Explode
if (!comp.Ruined && (comp.BladeHealth <= 0|| comp.RPM>comp.BestRPM*4))
{
TearApart(uid, comp);
}
// Send signals to device network
_signal.SendSignal(uid, comp.SpeedHighPort, comp.RPM > comp.BestRPM * 1.05);
_signal.SendSignal(uid, comp.SpeedLowPort, comp.RPM < comp.BestRPM * 0.95);
Dirty(uid, comp);
UpdateUI(uid, comp);
}
private float CalculateTransferVolume(TurbineComponent comp, PipeNode inlet, PipeNode outlet, float dt)
{
var wantToTransfer = comp.FlowRate * _atmosphereSystem.PumpSpeedup() * dt;
var transferVolume = Math.Min(inlet.Air.Volume, wantToTransfer);
var transferMoles = inlet.Air.Pressure * transferVolume / (inlet.Air.Temperature * Atmospherics.R);
var molesSpaceLeft = (comp.OutputPressure - outlet.Air.Pressure) * outlet.Air.Volume / (outlet.Air.Temperature * Atmospherics.R);
var actualMolesTransfered = Math.Clamp(transferMoles, 0, Math.Max(0, molesSpaceLeft));
return Math.Max(0, actualMolesTransfered * inlet.Air.Temperature * Atmospherics.R / inlet.Air.Pressure);
}
private void TearApart(EntityUid uid, TurbineComponent comp)
{
_audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/metal_break5.ogg"), uid, AudioParams.Default);
_popupSystem.PopupEntity(Loc.GetString("turbine-explode", ("owner", uid)), uid, PopupType.LargeCaution);
_explosion.QueueExplosion(uid, "Default", comp.RPM / 10, 15, 5, 0, canCreateVacuum: false);
if (comp.RPM > comp.BestRPM / 6) // If it's barely moving then there's not really reason it would throw shrapnel
ShootShrapnel(uid);
_adminLogger.Add(LogType.Explosion, LogImpact.High, $"{ToPrettyString(uid)} destroyed by overspeeding for too long");
comp.Ruined = true;
comp.RPM = 0;
_entityManager.QueueDeleteEntity(comp.CurrentBlade);
comp.CurrentBlade = null;
UpdateAppearance(uid, comp);
}
private void ShootShrapnel(EntityUid uid)
{
var ShrapnelCount = _random.Next(5, 20);
for (var i=0;i< ShrapnelCount; i++)
{
_gun.ShootProjectile(Spawn("TurbineBladeShrapnel", _transformSystem.GetMapCoordinates(uid)), _random.NextAngle().ToVec().Normalized(), _random.NextVector2(2, 6), uid, uid);
}
}
#endregion
#region BUI
public void UpdateUI(EntityUid uid, TurbineComponent turbine)
{
if (!_uiSystem.IsUiOpen(uid, TurbineUiKey.Key))
return;
_uiSystem.SetUiState(uid, TurbineUiKey.Key,
new TurbineBuiState
{
Overspeed = turbine.Overspeed,
Stalling = turbine.Stalling,
Overtemp = turbine.Overtemp,
Undertemp = turbine.Undertemp,
RPM = turbine.RPM,
BestRPM = turbine.BestRPM,
FlowRateMin = 0,
FlowRateMax = turbine.FlowRateMax,
FlowRate = turbine.FlowRate,
StatorLoadMin = 1000,
StatorLoad = turbine.StatorLoad,
PowerGeneration = turbine.SupplierMaxSupply,
PowerSupply = turbine.SupplierLastSupply,
Health = turbine.BladeHealth,
HealthMax = turbine.BladeHealthMax,
Blade = _entityManager.GetNetEntity(turbine.CurrentBlade),
Stator = _entityManager.GetNetEntity(turbine.CurrentStator),
});
}
private void OnTurbineFlowRateChanged(EntityUid uid, TurbineComponent turbine, TurbineChangeFlowRateMessage args)
{
if(TrySetFlowRate())
{
// Data is sent to a log queue to avoid spamming the admin log when adjusting values rapidly
var key = new KeyValuePair<EntityUid, EntityUid>(args.Actor, uid);
if(!_logQueue.TryGetValue(key, out var value))
_logQueue.Add(key, new LogData
{
CreationTime = _gameTiming.RealTime,
SetFlowRate = turbine.FlowRate
});
else
value.SetFlowRate = turbine.FlowRate;
}
UpdateUI(uid, turbine);
return;
bool TrySetFlowRate()
{
var newSet = Math.Clamp(args.FlowRate, 0f, turbine.FlowRateMax);
if (turbine.FlowRate != newSet)
{
turbine.FlowRate = newSet;
return true;
}
return false;
}
}
private void OnTurbineStatorLoadChanged(EntityUid uid, TurbineComponent turbine, TurbineChangeStatorLoadMessage args)
{
if (TrySetStatorLoad())
{
// Data is sent to a log queue to avoid spamming the admin log when adjusting values rapidly
var key = new KeyValuePair<EntityUid, EntityUid>(args.Actor, uid);
if (!_logQueue.TryGetValue(key, out var value))
_logQueue.Add(key, new LogData
{
CreationTime = _gameTiming.RealTime,
SetStatorLoad = turbine.StatorLoad
});
else
value.SetStatorLoad = turbine.StatorLoad;
}
UpdateUI(uid, turbine);
return;
bool TrySetStatorLoad()
{
var newSet = Math.Max(args.StatorLoad, 1000f);
if (turbine.StatorLoad != newSet)
{
turbine.StatorLoad = newSet;
return true;
}
return false;
}
}
private float _accumulator = 0f;
private readonly float _threshold = 0.5f;
public override void Update(float frameTime)
{
_accumulator += frameTime;
if (_accumulator > _threshold)
{
UpdateLogs();
_accumulator = 0;
}
return;
void UpdateLogs()
{
var toRemove = new List<KeyValuePair<EntityUid, EntityUid>>();
foreach (var log in _logQueue.Where(log => !((_gameTiming.RealTime - log.Value.CreationTime).TotalSeconds < 2)))
{
toRemove.Add(log.Key);
if (log.Value.SetFlowRate != null)
_adminLogger.Add(LogType.AtmosVolumeChanged, LogImpact.Medium,
$"{ToPrettyString(log.Key.Key):player} set the flow rate on {ToPrettyString(log.Key.Value):device} to {log.Value.SetFlowRate}");
if (log.Value.SetStatorLoad != null)
_adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium,
$"{ToPrettyString(log.Key.Key):player} set the stator load on {ToPrettyString(log.Key.Value):device} to {log.Value.SetStatorLoad}");
}
foreach (var kvp in toRemove)
_logQueue.Remove(kvp);
}
}
#endregion
private void OnSignalReceived(EntityUid uid, TurbineComponent comp, ref SignalReceivedEvent args)
{
var state = SignalState.Momentary;
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
if (args.Port == comp.StatorLoadIncreasePort)
comp.IncreasePortState = state;
else if (args.Port == comp.StatorLoadDecreasePort)
comp.DecreasePortState = state;
var logtext = "maintain";
if (comp.IncreasePortState != SignalState.Low && comp.DecreasePortState == SignalState.Low)
logtext = "increase";
else if (comp.DecreasePortState != SignalState.Low && comp.IncreasePortState == SignalState.Low)
logtext = "decrease";
_adminLogger.Add(LogType.Action, $"{ToPrettyString(args.Trigger):trigger} set the stator load on {ToPrettyString(uid):target} to {logtext}");
}
private void OnPortDisconnected(EntityUid uid, TurbineComponent comp, ref PortDisconnectedEvent args)
{
if (args.Port == comp.StatorLoadIncreasePort)
comp.IncreasePortState = SignalState.Low;
if (args.Port == comp.StatorLoadDecreasePort)
comp.DecreasePortState = SignalState.Low;
}
#region Anchoring
private void OnAnchorChanged(EntityUid uid, TurbineComponent comp, ref AnchorStateChangedEvent args)
{
if (!args.Anchored)
{
CleanUp(comp);
return;
}
}
private void OnUnanchorAttempt(EntityUid uid, TurbineComponent comp, ref UnanchorAttemptEvent args)
{
if (comp.RPM>1)
{
_popupSystem.PopupEntity(Loc.GetString("turbine-unanchor-warning"), args.User, args.User, PopupType.LargeCaution);
args.Cancel();
}
}
private bool GetPipes(EntityUid uid, TurbineComponent comp, [NotNullWhen(true)] out PipeNode? inlet, [NotNullWhen(true)] out PipeNode? outlet)
{
inlet = null;
outlet = null;
if (!comp.InletEnt.HasValue || EntityManager.Deleted(comp.InletEnt.Value))
comp.InletEnt = SpawnAttachedTo(comp.PipePrototype, new(uid, comp.InletPos), rotation: Angle.FromDegrees(comp.InletRot));
if (!comp.OutletEnt.HasValue || EntityManager.Deleted(comp.OutletEnt.Value))
comp.OutletEnt = SpawnAttachedTo(comp.PipePrototype, new(uid, comp.OutletPos), rotation: Angle.FromDegrees(comp.OutletRot));
if (comp.InletEnt == null || comp.OutletEnt == null)
return false;
if (!Transform(comp.InletEnt.Value).Anchored || !Transform(comp.OutletEnt.Value).Anchored)
{
_popupSystem.PopupEntity(Loc.GetString("turbine-anchor-warning"), uid, PopupType.MediumCaution);
CleanUp(comp);
_transform.Unanchor(uid);
return false;
}
if (!_nodeContainer.TryGetNode(comp.InletEnt.Value, comp.PipeName, out inlet))
return false;
if (!_nodeContainer.TryGetNode(comp.OutletEnt.Value, comp.PipeName, out outlet))
return false;
return true;
}
#endregion
private void CleanUp(TurbineComponent comp)
{
QueueDel(comp.InletEnt);
QueueDel(comp.OutletEnt);
}
private void OnDamaged(EntityUid uid, TurbineComponent comp, ref DamageChangedEvent args)
{
if (comp.Ruined)
return;
if (!args.DamageIncreased || args.DamageDelta == null)
return;
var damage = (float)args.DamageDelta.GetTotal();
var threshold = 50;
var ratio = damage / threshold;
if(ratio < 1)
{
comp.BladeHealth -= _random.Next(1, (int)(3f * ratio) + 1);
UpdateHealthIndicators(uid, comp);
return;
}
if (comp.RPM > comp.BestRPM / 6)
TearApart(uid, comp);
_entityManager.QueueDeleteEntity(comp.CurrentBlade);
comp.CurrentBlade = null;
if (_random.Prob(Math.Clamp(ratio - 1f, 0, 1)))
{
_entityManager.QueueDeleteEntity(comp.CurrentStator);
comp.CurrentStator = null;
}
comp.Ruined = true;
}
private void OnEjectAttempt(EntityUid uid, TurbineComponent comp, ref ItemSlotEjectAttemptEvent args)
{
if (args.Cancelled)
return;
if (comp.RPM < 1)
return;
args.Cancelled = true;
}
private void OnInsertAttempt(EntityUid uid, TurbineComponent comp, ref ItemSlotInsertAttemptEvent args)
{
if (args.Cancelled)
return;
if (comp.RPM < 1)
return;
args.Cancelled = true;
}
private void OnPartInserted(EntityUid uid, TurbineComponent comp, ref EntInsertedIntoContainerMessage args)
{
switch (args.Container.ID)
{
case BladeContainer:
comp.CurrentBlade = args.Container.ContainedEntities[0];
break;
case StatorContainer:
comp.CurrentStator = args.Container.ContainedEntities[0];
break;
default:
return;
}
UpdatePartValues(comp);
}
private void OnPartEjected(EntityUid uid, TurbineComponent comp, ref EntRemovedFromContainerMessage args)
{
switch (args.Container.ID)
{
case BladeContainer:
comp.CurrentBlade = null;
break;
case StatorContainer:
comp.CurrentStator = null;
break;
default:
return;
}
UpdatePartValues(comp);
}
private void UpdatePartValues(TurbineComponent comp)
{
_entityManager.TryGetComponent<GasTurbineBladeComponent>(comp.CurrentBlade, out var bladeComp);
_entityManager.TryGetComponent<GasTurbineStatorComponent>(comp.CurrentStator, out var statorComp);
if (bladeComp != null)
{
comp.TurbineMass = Math.Max(200, 200 * bladeComp.Properties.Density);
comp.BladeHealthMax = (int)Math.Max(1, 5 * bladeComp.Properties.Hardness);
comp.BladeHealth = comp.BladeHealthMax;
}
if (statorComp != null)
{
comp.PowerMultiplier = (float)Math.Max(0.2, 0.2 * statorComp.Properties.ElectricalConductivity);
}
}
}
@@ -1,3 +1,4 @@
using Content.Shared._FarHorizons.Materials; //Far Horizons Nuclear Reactor Port
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Utility;
@@ -56,5 +57,12 @@ namespace Content.Shared.Materials
/// </summary>
[DataField(required: true)]
public double Price = 0;
// Far Horizons Edit
/// <summary>
/// The physical properties of this material.
/// </summary>
[DataField("properties")]
public MaterialProperties Properties { get; private set; } = new();
}
}
@@ -0,0 +1,64 @@
namespace Content.Shared._FarHorizons.Materials;
/// <summary>
/// A data type that stores information on a material's properties
/// </summary>
[DataDefinition]
public sealed partial class MaterialProperties()
{
[DataField("electrical")]
public float ElectricalConductivity { get; set; } = 5;
[DataField("thermal")]
public float ThermalConductivity { get; set; } = 5;
[DataField("hard")]
public float Hardness { get; set; } = 3;
[DataField("density")]
public float Density { get; set; } = 3;
[DataField("reflective")]
public float Reflectivity { get; set; } = 0;
[DataField("flammable")]
public float Flammability { get; set; } = 1;
[DataField("chemical")]
public float ChemicalResistance { get; set; } = 3;
[DataField("radioactive")]
public float Radioactivity { get; set; } = 0;
[DataField("n_radioactive")]
public float NeutronRadioactivity { get; set; } = 0;
[DataField("spent_fuel")]
public float FissileIsotopes { get; set; } = 0;
[DataField("molitz_bubbles")]
public float GasPockets { get; set; } = 0;
[DataField("plasma_offgas")]
public float ActivePlasma { get; set; } = 0;
/// <summary>
/// Creates a new <see cref="MaterialProperties"> with information from an existing one.
/// </summary>
/// <param name="source"></param>
public MaterialProperties(MaterialProperties source) : this()
{
ElectricalConductivity = source.ElectricalConductivity;
ThermalConductivity = source.ThermalConductivity;
Hardness = source.Hardness;
Density = source.Density;
Reflectivity = source.Reflectivity;
Flammability = source.Flammability;
ChemicalResistance = source.ChemicalResistance;
Radioactivity = source.Radioactivity;
NeutronRadioactivity = source.NeutronRadioactivity;
FissileIsotopes = source.FissileIsotopes;
GasPockets = source.GasPockets;
ActivePlasma = source.ActivePlasma;
}
}
@@ -0,0 +1,27 @@
namespace Content.Shared._FarHorizons.Materials.Systems;
public sealed class MaterialSystem
{
public static double CalculateHeatTransferCoefficient(MaterialProperties? materialA, MaterialProperties? materialB)
{
var hTC1 = 5.0;
var hTC2 = 5.0;
if (materialA != null)
if (materialA.ThermalConductivity > 0 && materialA.ElectricalConductivity > 0)
hTC1 = (Math.Max(materialA.ThermalConductivity, 0) + Math.Max(materialA.ElectricalConductivity, 0)) / 2;
else if (materialA.ThermalConductivity > 0)
hTC1 = Math.Max(materialA.ThermalConductivity, 0);
else if (materialA.ElectricalConductivity > 0)
hTC1 = Math.Max(materialA.ElectricalConductivity, 0);
if (materialB != null)
if (materialB.ThermalConductivity > 0 && materialB.ElectricalConductivity > 0)
hTC2 = (Math.Max(materialB.ThermalConductivity, 0) + Math.Max(materialB.ElectricalConductivity, 0)) / 2;
else if (materialB.ThermalConductivity > 0)
hTC2 = Math.Max(materialB.ThermalConductivity, 0);
else if (materialB.ElectricalConductivity > 0)
hTC2 = Math.Max(materialB.ElectricalConductivity, 0);
return ((Math.Pow(10, hTC1 / 5) - 1) + (Math.Pow(10, hTC2 / 5) - 1)) / 2;
}
}
@@ -0,0 +1,15 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class GasTurbineMonitorComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public NetEntity? turbine;
[DataField]
public ProtoId<SinkPortPrototype> LinkingPort = "GasTurbineDataReceiver";
}
@@ -0,0 +1,34 @@
using Content.Shared._FarHorizons.Materials;
using Content.Shared.Materials;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
public abstract partial class GasTurbinePartComponent : Component
{
[Dependency] private IPrototypeManager _proto = default!;
[DataField("material")]
public ProtoId<MaterialPrototype> Material = "Steel";
public MaterialProperties Properties
{
get
{
IoCManager.Resolve(ref _proto);
_properties ??= new MaterialProperties(_proto.Index(Material).Properties);
return _properties;
}
set => _properties = value;
}
[DataField("properties")]
private MaterialProperties? _properties;
}
[RegisterComponent, NetworkedComponent]
public sealed partial class GasTurbineBladeComponent : GasTurbinePartComponent;
[RegisterComponent, NetworkedComponent]
public sealed partial class GasTurbineStatorComponent : GasTurbinePartComponent;
@@ -0,0 +1,336 @@
using Robust.Shared.GameStates;
using Robust.Shared.Audio;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Atmos;
using Robust.Shared.Prototypes;
using Content.Shared.Materials;
using Content.Shared.DeviceLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization;
using System.Numerics;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/nuclearreactor.dm
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class NuclearReactorComponent : Component
{
/// <summary>
/// Width of the reactor grid
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public int ReactorGridWidth = 7;
/// <summary>
/// Height of the reactor grid
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public int ReactorGridHeight = 7;
public readonly int ReactorOverheatTemp = 1200;
public readonly int ReactorFireTemp = 1500;
public readonly int ReactorMeltdownTemp = 2000;
// Making this a DataField causes the game to explode, neat
/// <summary>
/// 2D grid of reactor components, or null where there are no components. Size is ReactorGridWidth x ReactorGridHeight
/// </summary>
public ReactorPartComponent?[,] ComponentGrid;
/// <summary>
/// Dictionary of data that determines the reactor grid's visuals
/// </summary>
[AutoNetworkedField]
public Dictionary<Vector2i, ReactorCapVisualData> VisualData = [];
// Woe, 3 dimensions be upon ye
/// <summary>
/// 2D grid of lists of neutrons in each grid slot of the component grid
/// </summary>
public List<ReactorNeutron>[,] FluxGrid;
/// <summary>
/// Number of neutrons that hit the edge of the reactor grid last tick
/// </summary>
[ViewVariables]
public float RadiationLevel = 0;
/// <summary>
/// Gas mixture currently in the reactor
/// </summary>
public GasMixture? AirContents;
/// <summary>
/// Reactor casing temperature
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float Temperature = Atmospherics.T20C;
/// <summary>
/// Thermal mass. Basically how much energy it takes to heat this up 1Kelvin
/// </summary>
[DataField]
public float ThermalMass = 420 * 2000; // specific heat capacity of steel (420 J/KgK) * mass of reactor (Kg)
/// <summary>
/// Volume of gas to process each tick
/// </summary>
[DataField]
public float ReactorVesselGasVolume = 200;
/// <summary>
/// Flag indicating the reactor is overheating
/// </summary>
[ViewVariables]
public bool IsSmoking = false;
/// <summary>
/// Flag indicating the reactor is on fire
/// </summary>
[ViewVariables]
public bool IsBurning = false;
/// <summary>
/// Flag indicating total meltdown has happened
/// </summary>
[DataField, ViewVariables, AutoNetworkedField]
public bool Melted = false;
/// <summary>
/// The set insertion level of the control rods
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float ControlRodInsertion = 2;
/// <summary>
/// The actual insertion level of the control rods
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float AvgInsertion = 0;
/// <summary>
/// Sound that plays globally on meltdown
/// </summary>
public SoundSpecifier MeltdownSound = new SoundPathSpecifier("/Audio/_FarHorizons/Machines/meltdown_siren.ogg");
/// <summary>
/// Radio channel to send alerts to
/// </summary>
[DataField]
public string EngineeringChannel = "Engineering";
/// <summary>
/// Last reported temperature during overheat events
/// </summary>
[ViewVariables]
public float LastSendTemperature = Atmospherics.T20C;
/// <summary>
/// If the reactor has given the nuclear emergency warning
/// </summary>
[ViewVariables]
public bool HasSentWarning = false;
/// <summary>
/// Alert level to set after meltdown
/// </summary>
[DataField]
public string MeltdownAlertLevel = "yellow";
/// <summary>
/// The minimum radiation from the melted reactor
/// </summary>
[DataField]
public float MeltdownRadiation = 10;
/// <summary>
/// How quickly radiation decreases
/// </summary>
/// <remarks>Cannot be less than 1</remarks>
[DataField]
public float RadiationStability = 2;
/// <summary>
/// The soft maximum radiation the reactor is expected to produce, beyond which radiation increases logarithmically. Also used for alarms and UI.
/// </summary>
[DataField]
public float MaximumRadiation = 50;
/// <summary>
/// The maximum thermal power the reactor is expected to produce
/// </summary>
/// <remarks>This will NOT stop the reactor from making more than this value</remarks>
[DataField]
public float MaximumThermalPower = 10000000;
/// <summary>
/// The estimated thermal power the reactor is making
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float ThermalPower = 0;
public int ThermalPowerCount = 0;
public int ThermalPowerPrecision = 128;
[ViewVariables]
public EntityUid? AlarmAudioHighThermal;
[ViewVariables]
public EntityUid? AlarmAudioHighTemp;
[ViewVariables]
public EntityUid? AlarmAudioHighRads;
[ViewVariables]
public ItemSlot PartSlot = new();
/// <summary>
/// Grid of temperature values
/// </summary>
public double[,] TemperatureGrid;
/// <summary>
/// Grid of neutron counts
/// </summary>
public int[,] NeutronGrid;
/// <summary>
/// The selected prefab
/// </summary>
[DataField]
public string Prefab = "ReactorPrefab7x7Normal";
/// <summary>
/// Flag indicating the reactor should apply the selected prefab
/// </summary>
[DataField]
public bool ApplyPrefab = false;
/// <summary>
/// Chance that a reactor slot is filled when applying the random prefab
/// </summary>
[DataField]
public float RandomPrefabFill = 0.3f;
/// <summary>
/// Material the reactor is made out of
/// </summary>
[DataField("material")]
public ProtoId<MaterialPrototype> Material = "Steel";
/// <summary>
/// Determines the spacing and position of the visual grid. Measured in pixels.
/// </summary>
/// <remarks>
/// [0] Spacing along the x axis<br/>
/// [1] Spacing along the y axis<br/>
/// [2] Offset of the center along the x axis<br/>
/// [3] Offset of the center along the y axis
/// </remarks>
[DataField]
public int[] Gridbounds = [ 18, 15, 0, 5 ];
#region Pipe Connections
/// <summary>
/// Name of the pipe node
/// </summary>
[DataField]
public string PipeName { get; set; } = "pipe";
/// <summary>
/// Inlet entity
/// </summary>
[ViewVariables]
public EntityUid? InletEnt;
/// <summary>
/// Position of the inlet entity
/// </summary>
[DataField]
public Vector2 InletPos = new(-2, -1);
/// <summary>
/// Rotation of the inlet entity, in degrees
/// </summary>
[DataField]
public float InletRot = -90;
/// <summary>
/// Outlet entity
/// </summary>
[ViewVariables]
public EntityUid? OutletEnt;
/// <summary>
/// Position of the outlet entity
/// </summary>
[DataField]
public Vector2 OutletPos = new(2, 1);
/// <summary>
/// Rotation of the outlet entity, in degrees
/// </summary>
[DataField]
public float OutletRot = 90;
/// <summary>
/// Name of the prototype of the arrows that indicate flow on inspect
/// </summary>
[DataField]
public EntProtoId ArrowPrototype = "ReactorFlowArrow";
/// <summary>
/// Name of the prototype of the pipes the reactor uses to connect to the pipe network
/// </summary>
[DataField]
public EntProtoId PipePrototype = "ReactorGasPipe";
#endregion
#region Device Network
/// <summary>
/// The proto ID of the "Retract Control Rods" sink port
/// </summary>
[DataField("controlRodRetractPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string ControlRodRetractPort = "RetractControlRods";
/// <summary>
/// The proto ID of the "Insert Control Rods" sink port
/// </summary>
[DataField("controlRodInsertPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string ControlRodInsertPort = "InsertControlRods";
/// <summary>
/// The signal state of the retract control rods port
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SignalState RetractPortState = SignalState.Low;
/// <summary>
/// The signal state of the insert control rods port
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SignalState InsertPortState = SignalState.Low;
#endregion
#region Debug
[ViewVariables(VVAccess.ReadOnly)]
public int NeutronCount = 0;
[ViewVariables(VVAccess.ReadOnly)]
public int MeltedParts = 0;
[ViewVariables(VVAccess.ReadOnly)]
public int DetectedControlRods = 0;
[ViewVariables(VVAccess.ReadOnly)]
public float TotalNRads = 0;
[ViewVariables(VVAccess.ReadOnly)]
public float TotalRads = 0;
[ViewVariables(VVAccess.ReadOnly)]
public float TotalSpent = 0;
#endregion
}
[Serializable, NetSerializable, DataDefinition]
public sealed partial class ReactorCapVisualData
{
public Color color = Color.Black;
public string cap = "";
}
@@ -0,0 +1,15 @@
using Content.Shared.DeviceLinking;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class NuclearReactorMonitorComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public NetEntity? reactor;
[DataField]
public ProtoId<SinkPortPrototype> LinkingPort = "NuclearReactorDataReceiver";
}
@@ -0,0 +1,14 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
[Prototype]
public sealed partial class NuclearReactorPrefabPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField("parts")]
public Dictionary<Vector2i, EntProtoId> ReactorComponents { get; private set; } = [];
}
@@ -0,0 +1,192 @@
using Content.Shared._FarHorizons.Materials;
using Content.Shared.Atmos;
using Content.Shared.Materials;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/reactorcomponents.dm
/// <summary>
/// A reactor part for the reactor grid.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ReactorPartComponent : Component
{
[Dependency] private IPrototypeManager _proto = default!;
/// <summary>
/// The entity prototype name this component results from.
/// </summary>
[DataField]
public EntProtoId ProtoId = "BaseReactorPart";
/// <summary>
/// Icon of this component as it shows in the UIs.
/// </summary>
[DataField]
public string IconStateInserted = "base";
/// <summary>
/// Icon of this component as it shows in the world.
/// </summary>
[DataField]
public string IconStateCap = "rod_cap";
/// <summary>
/// Byte indicating what type of rod this reactor part is
/// </summary>
[DataField]
public int RodType = 0;
public enum RodTypes
{
None = 0,
FuelRod = 1 << 0, // 1 Can be processed by the nuclear centrifuge
ControlRod = 1 << 1, // 2 Can change its NeutronCrossSection according to control rod setting
GasChannel = 1 << 2, // 4 Can process gas
}
#region Variables
/// <summary>
/// Temperature of this component, starts at room temp Kelvin by default.
/// </summary>
[DataField]
public float Temperature = Atmospherics.T20C;
/// <summary>
/// How much does this component share heat with surrounding components? Basically surface area in contact (m2).
/// </summary>
[DataField]
public float ThermalCrossSection = 10;
/// <summary>
/// How adept is this component at interacting with neutrons - fuel rods are set up to capture them, heat exchangers are set up not to.
/// </summary>
[DataField]
public float NeutronCrossSection = 0.5f;
/// <summary>
/// Control rods don't moderate neutrons, they absorb them.
/// </summary>
[DataField]
public bool IsControlRod = false;
/// <summary>
/// Max health to set <see cref="MeltHealth"/> to on init.
/// </summary>
[DataField]
public float MaxHealth = 100;
/// <summary>
/// Essentially indicates how long this component can be at a dangerous temperature before it melts.
/// </summary>
[DataField]
public float MeltHealth = 100;
/// <summary>
/// If this component is melted, you can't take it out of the reactor and it might do some weird stuff.
/// </summary>
[DataField]
public bool Melted = false;
/// <summary>
/// The dangerous temperature above which this component starts to melt. 1700K is the melting point of steel.
/// </summary>
[DataField]
public float MeltingPoint = 1700;
/// <summary>
/// How much gas this component can hold, and will be processed per tick.
/// </summary>
[DataField]
public float GasVolume = 0;
/// <summary>
/// Thermal mass. Basically how much energy it takes to heat this up 1Kelvin.
/// </summary>
[DataField]
public float ThermalMass = 420 * 250; //specific heat capacity of steel (420 J/KgK) * mass of component (Kg)
#endregion
[DataField("material")]
public ProtoId<MaterialPrototype> Material = "Steel";
public MaterialProperties Properties
{
get
{
IoCManager.Resolve(ref _proto);
_properties ??= new MaterialProperties(_proto.Index(Material).Properties);
return _properties;
}
set => _properties = value;
}
[DataField("properties")]
private MaterialProperties? _properties;
#region Type specific
/// <summary>
/// The target insertion level of the control rod.
/// </summary>
[DataField]
public float ConfiguredInsertionLevel = 1;
/// <summary>
/// How adept the gas channel is at transfering heat to/from gasses.
/// </summary>
[DataField]
public float GasThermalCrossSection = 15; //was 15
/// <summary>
/// The gas mixture inside the gas channel.
/// </summary>
public GasMixture? AirContents;
#endregion
/// <summary>
/// Creates a new <see cref="ReactorPartComponent"> with information from an existing one.
/// </summary>
/// <param name="source"></param>
public ReactorPartComponent(ReactorPartComponent source)
{
ProtoId = source.ProtoId;
IconStateInserted = source.IconStateInserted;
IconStateCap = source.IconStateCap;
RodType = source.RodType;
Temperature = source.Temperature;
ThermalCrossSection = source.ThermalCrossSection;
NeutronCrossSection = source.NeutronCrossSection;
IsControlRod = source.IsControlRod;
MaxHealth = source.MaxHealth;
MeltHealth = source.MeltHealth;
Melted = source.Melted;
MeltingPoint = source.MeltingPoint;
GasVolume = source.GasVolume;
ThermalMass = source.ThermalMass;
Material = source.Material;
_properties = source._properties;
ConfiguredInsertionLevel = source.ConfiguredInsertionLevel;
GasThermalCrossSection = source.GasThermalCrossSection;
AirContents = source.AirContents;
}
public bool HasRodType(RodTypes type) => (RodType & (int)type) == (int)type;
}
/// <summary>
/// A virtual neutron that flies around within the reactor.
/// </summary>
[NetworkedComponent]
public sealed class ReactorNeutron
{
public Direction dir = Direction.North;
public float velocity = 1;
}
@@ -0,0 +1,21 @@
using Robust.Shared.Serialization;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
/// <summary>
///
/// </summary>
[Serializable, NetSerializable]
public enum NuclearCentrifugeVisuals
{
Processing
}
/// <summary>
///
/// </summary>
[Serializable, NetSerializable]
public enum NuclearCentrifugeVisualLayers
{
Processing
}
@@ -0,0 +1,55 @@
using Robust.Shared.Serialization;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
[Serializable, NetSerializable]
public enum NuclearReactorUiKey : byte
{
Key,
}
[Serializable, NetSerializable]
public sealed class NuclearReactorBuiState : BoundUserInterfaceState
{
public Dictionary<Vector2i, ReactorSlotBUIData> SlotData = [];
public int GridWidth = 0;
public int GridHeight = 0;
public string? ItemName;
public float ReactorTemp = 0;
public float ReactorRads = 0;
public float ReactorRadsMax = 0;
public float ReactorTherm = 0;
public float ControlRodActual = 0;
public float ControlRodSet = 0;
}
[Serializable, NetSerializable, DataDefinition]
public sealed partial class ReactorSlotBUIData
{
public double Temperature = 0f;
public int NeutronCount = 0;
public string IconName = "base";
public string PartName = "empty";
public float NeutronRadioactivity = 0f;
public float Radioactivity = 0f;
public float SpentFuel = 0f;
}
[Serializable, NetSerializable]
public sealed class ReactorItemActionMessage(Vector2d position) : BoundUserInterfaceMessage
{
public Vector2d Position { get; } = position;
}
[Serializable, NetSerializable]
public sealed class ReactorEjectItemMessage() : BoundUserInterfaceMessage;
[Serializable, NetSerializable]
public sealed class ReactorControlRodModifyMessage(float change) : BoundUserInterfaceMessage
{
public float Change { get; } = change;
}
@@ -0,0 +1,122 @@
using Content.Shared.Popups;
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/nuclearreactor.dm
public abstract class SharedNuclearReactorSystem : EntitySystem
{
[Dependency] private readonly ItemSlotsSystem _slotsSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();
// BUI event
SubscribeLocalEvent<NuclearReactorComponent, ReactorEjectItemMessage>(OnEjectItemMessage);
}
private void OnEjectItemMessage(EntityUid uid, NuclearReactorComponent component, ReactorEjectItemMessage args)
{
if (component.PartSlot.Item == null)
return;
_slotsSystem.TryEjectToHands(uid, component.PartSlot, args.Actor);
}
protected bool ReactorTryGetSlot(EntityUid uid, string slotID, out ItemSlot? itemSlot) => _slotsSystem.TryGetSlot(uid, slotID, out itemSlot);
public void UpdateGridVisual(Entity<NuclearReactorComponent> ent)
{
var comp = ent.Comp;
var uid = ent.Owner;
if (comp.ComponentGrid == null)
return;
for (var x = 0; x < comp.ReactorGridWidth; x++)
{
for (var y = 0; y < comp.ReactorGridHeight; y++)
{
var gridComp = comp.ComponentGrid[x, y];
var vector = new Vector2i(x, y);
if (gridComp == null)
{
comp.VisualData.Remove(vector);
}
else
{
var data = new ReactorCapVisualData { cap = gridComp.IconStateCap, color = _proto.Index(gridComp.Material).Color };
if (!comp.VisualData.TryAdd(vector, data))
comp.VisualData[vector] = data;
}
}
}
Dirty(ent);
// Sanity check to make sure there is actually an appearance component (nullpointer hell)
if (!_entityManager.HasComponent<AppearanceComponent>(uid))
return;
// The data being set doesn't really matter, it just has to trigger AppearanceChangeEvent and the client will handle the rest
if (!_appearance.TryGetData(uid, ReactorCapVisuals.Sprite, out bool prevValue))
_appearance.SetData(uid, ReactorCapVisuals.Sprite, true);
_appearance.SetData(uid, ReactorCapVisuals.Sprite, !prevValue);
}
protected void UpdateTempIndicators(Entity<NuclearReactorComponent> ent)
{
var comp = ent.Comp;
var uid = ent.Owner;
if (comp.Temperature >= comp.ReactorOverheatTemp)
{
if(!comp.IsSmoking)
{
comp.IsSmoking = true;
_appearance.SetData(uid, ReactorVisuals.Smoke, true);
_popupSystem.PopupEntity(Loc.GetString("reactor-smoke-start", ("owner", uid)), uid, PopupType.MediumCaution);
}
if (comp.Temperature >= comp.ReactorFireTemp && !comp.IsBurning)
{
comp.IsBurning = true;
_appearance.SetData(uid, ReactorVisuals.Fire, true);
_popupSystem.PopupEntity(Loc.GetString("reactor-fire-start", ("owner", uid)), uid, PopupType.MediumCaution);
}
else if (comp.Temperature < comp.ReactorFireTemp && comp.IsBurning)
{
comp.IsBurning = false;
_appearance.SetData(uid, ReactorVisuals.Fire, false);
_popupSystem.PopupEntity(Loc.GetString("reactor-fire-stop", ("owner", uid)), uid, PopupType.Medium);
}
}
else
{
if(comp.IsSmoking)
{
comp.IsSmoking = false;
_appearance.SetData(uid, ReactorVisuals.Smoke, false);
_popupSystem.PopupEntity(Loc.GetString("reactor-smoke-stop", ("owner", uid)), uid, PopupType.Medium);
}
}
}
public static bool AdjustControlRods(NuclearReactorComponent comp, float change) {
var newSet = Math.Clamp(comp.ControlRodInsertion + change, 0, 2);
if (comp.ControlRodInsertion != newSet)
{
comp.ControlRodInsertion = newSet;
return true;
}
return false;
}
}
@@ -0,0 +1,79 @@
using Robust.Shared.Serialization;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
#region Reactor Caps
/// <summary>
/// Appearance key for the reactor caps.
/// </summary>
[Serializable, NetSerializable]
public enum ReactorCapVisuals
{
Sprite
}
#endregion
#region Reactor
/// <summary>
/// Appearance keys for the reactor.
/// </summary>
[Serializable, NetSerializable]
public enum ReactorVisuals
{
Sprite,
Status,
Input,
Output,
Lights,
Smoke,
Fire,
}
/// <summary>
/// Visual sprite layers for the reactor.
/// </summary>
[Serializable, NetSerializable]
public enum ReactorVisualLayers
{
Sprite,
Status,
Input,
Output,
Lights,
Smoke,
Fire,
}
/// <summary>
/// Reactor sprites.
/// </summary>
[Serializable, NetSerializable]
public enum Reactors
{
Normal,
Melted,
}
/// <summary>
/// Status screens.
/// </summary>
[Serializable, NetSerializable]
public enum ReactorStatusLights
{
Off,
Active,
Overheat,
Meltdown,
}
/// <summary>
/// Warning lights settings.
/// </summary>
[Serializable, NetSerializable]
public enum ReactorWarningLights
{
LightsOff,
LightsWarning,
LightsMeltdown,
}
#endregion
@@ -0,0 +1,2 @@
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
public abstract class SharedReactorPartSystem : EntitySystem;
@@ -0,0 +1,56 @@
using Robust.Shared.Serialization;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
[Serializable, NetSerializable]
public enum TurbineUiKey : byte
{
Key,
}
[Serializable, NetSerializable]
public sealed class TurbineBuiState : BoundUserInterfaceState
{
// Indicator Lights
public bool Overspeed;
public bool Stalling;
public bool Overtemp;
public bool Undertemp;
// Speed
public float RPM;
public float BestRPM;
// Flow rate
public float FlowRateMin;
public float FlowRateMax;
public float FlowRate;
// Stator load
public float StatorLoadMin;
public float StatorLoad;
// Power generation
public float PowerGeneration;
public float PowerSupply;
// Health
public float Health;
public float HealthMax;
// Parts
public NetEntity? Blade;
public NetEntity? Stator;
}
[Serializable, NetSerializable]
public sealed class TurbineChangeFlowRateMessage(float flowRate) : BoundUserInterfaceMessage
{
public float FlowRate { get; } = flowRate;
}
[Serializable, NetSerializable]
public sealed class TurbineChangeStatorLoadMessage(float statorLoad) : BoundUserInterfaceMessage
{
public float StatorLoad { get; } = statorLoad;
}
@@ -0,0 +1,223 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Electrocution;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Repairable;
using Content.Shared.Tools.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/turbine.dm
public abstract class SharedTurbineSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] protected readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TurbineComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<TurbineComponent, InteractUsingEvent>(RepairTurbine);
SubscribeLocalEvent<TurbineComponent, RepairDoAfterEvent>(OnRepairTurbineFinished);
}
private void OnExamined(Entity<TurbineComponent> ent, ref ExaminedEvent args)
{
var comp = ent.Comp;
if (!Comp<TransformComponent>(ent).Anchored || !args.IsInDetailsRange) // Not anchored? Out of range? No status.
return;
using (args.PushGroup(nameof(TurbineComponent)))
{
if(comp.CurrentStator == null)
args.PushMarkup(Loc.GetString("gas-turbine-examine-stator-null"));
if (comp.CurrentBlade == null)
args.PushMarkup(Loc.GetString("gas-turbine-examine-blade-null"));
else
{
switch (comp.RPM)
{
case float n when n is >= 0 and <= 1:
args.PushMarkup(Loc.GetString("turbine-spinning-0")); // " The blades are not spinning."
break;
case float n when n is > 1 and <= 60:
args.PushMarkup(Loc.GetString("turbine-spinning-1")); // " The blades are turning slowly."
break;
case float n when n > 60 && n <= comp.BestRPM * 0.5:
args.PushMarkup(Loc.GetString("turbine-spinning-2")); // " The blades are spinning."
break;
case float n when n > comp.BestRPM * 0.5 && n <= comp.BestRPM * 1.2:
args.PushMarkup(Loc.GetString("turbine-spinning-3")); // " The blades are spinning quickly."
break;
case float n when n > comp.BestRPM * 1.2 && n <= float.PositiveInfinity:
args.PushMarkup(Loc.GetString("turbine-spinning-4")); // " The blades are spinning out of control!"
break;
default:
break;
}
}
if (comp.Ruined)
{
args.PushMarkup(Loc.GetString("turbine-ruined")); // " It's completely broken!"
}
else if (comp.BladeHealth <= 0.25 * comp.BladeHealthMax)
{
args.PushMarkup(Loc.GetString("turbine-damaged-3")); // " It's critically damaged!"
}
else if (comp.BladeHealth <= 0.5 * comp.BladeHealthMax)
{
args.PushMarkup(Loc.GetString("turbine-damaged-2")); // " The turbine looks badly damaged."
}
else if (comp.BladeHealth <= 0.75 * comp.BladeHealthMax)
{
args.PushMarkup(Loc.GetString("turbine-damaged-1")); // " The turbine looks a bit scuffed."
}
else
{
args.PushMarkup(Loc.GetString("turbine-damaged-0")); // " It appears to be in good condition."
}
}
}
protected void UpdateAppearance(EntityUid uid, TurbineComponent? comp = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref comp, ref appearance, false))
return;
_appearance.SetData(uid, TurbineVisuals.TurbineRuined, comp.Ruined);
_appearance.SetData(uid, TurbineVisuals.DamageSpark, comp.IsSparking);
_appearance.SetData(uid, TurbineVisuals.DamageSmoke, comp.IsSmoking);
}
protected void PlayAudio(SoundSpecifier? sound, EntityUid uid, out EntityUid? audioStream, AudioParams? audioParams = null)
{
if (sound == null || audioParams == null)
{
audioStream = null;
return;
}
var loop = audioParams.Value.WithLoop(true);
var stream = false
? _audio.PlayPredicted(sound, uid, uid, loop)
: _audio.PlayPvs(sound, uid, loop);
audioStream = stream?.Entity is { } entity ? entity : null;
}
protected static bool AdjustStatorLoad(TurbineComponent turbine, float change)
{
var newSet = Math.Max(turbine.StatorLoad + change, 1000f);
if (turbine.StatorLoad != newSet)
{
turbine.StatorLoad = newSet;
return true;
}
return false;
}
#region Repairs
private void RepairTurbine(EntityUid uid, TurbineComponent comp, ref InteractUsingEvent args)
{
if (args.Handled)
return;
if(_toolSystem.HasQuality(args.Used, comp.RepairTool))
{
if (comp.CurrentBlade == null)
{
_popupSystem.PopupEntity(Loc.GetString("gas-turbine-repair-fail-blade"), args.User, args.User, PopupType.Medium);
args.Handled = true;
return;
}
if (comp.CurrentStator == null)
{
_popupSystem.PopupEntity(Loc.GetString("gas-turbine-repair-fail-stator"), args.User, args.User, PopupType.Medium);
args.Handled = true;
return;
}
if (comp.BladeHealth >= comp.BladeHealthMax && !comp.Ruined)
return;
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, comp.RepairDelay, comp.RepairTool, new RepairDoAfterEvent(), comp.RepairFuelCost);
}
}
//Gotta love server/client desync
protected virtual void OnRepairTurbineFinished(EntityUid uid, TurbineComponent comp, ref RepairDoAfterEvent args)
{
if (comp.Ruined)
{
comp.Ruined = false;
if (comp.BladeHealth <= 0) { comp.BladeHealth = 1; }
UpdateHealthIndicators(uid, comp);
}
else if (comp.BladeHealth < comp.BladeHealthMax)
{
comp.BladeHealth++;
UpdateHealthIndicators(uid, comp);
}
else if (comp.BladeHealth >= comp.BladeHealthMax)
{
// This should technically never occur, but just in case...
}
if (!_entityManager.TryGetComponent<DamageableComponent>(uid, out var damageableComponent))
return;
_damageableSystem.SetAllDamage((uid, damageableComponent), 0);
}
protected void UpdateHealthIndicators(EntityUid uid, TurbineComponent comp)
{
if (comp.BladeHealth <= 0.75 * comp.BladeHealthMax && !comp.IsSparking)
{
comp.IsSparking = true;
_audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/PowerSink/electric.ogg"), uid, AudioParams.Default.WithPitchScale(0.75f));
_popupSystem.PopupEntity(Loc.GetString("turbine-spark", ("owner", uid)), uid, PopupType.MediumCaution);
}
else if (comp.BladeHealth > 0.75 * comp.BladeHealthMax && comp.IsSparking)
{
comp.IsSparking = false;
_popupSystem.PopupEntity(Loc.GetString("turbine-spark-stop", ("owner", uid)), uid, PopupType.Medium);
}
if (comp.BladeHealth <= 0.5 * comp.BladeHealthMax && !comp.IsSmoking)
{
comp.IsSmoking = true;
_popupSystem.PopupEntity(Loc.GetString("turbine-smoke", ("owner", uid)), uid, PopupType.MediumCaution);
}
else if (comp.BladeHealth > 0.5 * comp.BladeHealthMax && comp.IsSmoking)
{
comp.IsSmoking = false;
_popupSystem.PopupEntity(Loc.GetString("turbine-smoke-stop", ("owner", uid)), uid, PopupType.Medium);
}
_entityManager.EnsureComponent<ElectrifiedComponent>(uid).Enabled = comp.IsSparking;
UpdateAppearance(uid, comp);
}
#endregion
}
@@ -0,0 +1,27 @@
using Robust.Shared.Serialization;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
/// <summary>
/// Appearance keys for the turbine.
/// </summary>
[Serializable, NetSerializable]
public enum TurbineVisuals
{
TurbineRuined,
DamageSpark,
DamageSmoke,
TurbineSpeed,
}
/// <summary>
/// Visual sprite layers for the turbine.
/// </summary>
[Serializable, NetSerializable]
public enum TurbineVisualLayers
{
TurbineRuined,
DamageSpark,
DamageSmoke,
TurbineSpeed,
}
@@ -0,0 +1,288 @@
using Robust.Shared.Prototypes;
using Robust.Shared.GameStates;
using Content.Shared.Tools;
using Content.Shared.Atmos;
using Content.Shared.DeviceLinking;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using System.Numerics;
namespace Content.Shared._FarHorizons.Power.Generation.FissionGenerator;
// Ported and modified from goonstation by Jhrushbe.
// CC-BY-NC-SA-3.0
// https://github.com/goonstation/goonstation/blob/ff86b044/code/obj/nuclearreactor/turbine.dm
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class TurbineComponent : Component
{
/// <summary>
/// Power generated last tick
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public float LastGen = 0;
/// <summary>
/// Watts per revolution
/// </summary>
[DataField, AutoNetworkedField]
public float StatorLoad = 35000;
/// <summary>
/// Maximum setting of stator load
/// </summary>
// [DataField]
// public float StatorLoadMax = 500000;
/// <summary>
/// Current RPM of turbine
/// </summary>
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public float RPM = 0;
/// <summary>
/// Turbine's resistance to change in RPM
/// </summary>
[DataField]
public float TurbineMass = 1000;
/// <summary>
/// Most efficient power generation at this value, overspeed at 1.2*this
/// </summary>
[DataField]
public float BestRPM = 600;
/// <summary>
/// RPM the animation is playing at
/// </summary>
[ViewVariables]
public float AnimRPM = 0;
/// <summary>
/// Volume of gas to process per tick for power generation
/// </summary>
[DataField, AutoNetworkedField]
public float FlowRate = Atmospherics.MaxTransferRate;
/// <summary>
/// Maximum volume of gas to process per tick
/// </summary>
[DataField]
public float FlowRateMax = Atmospherics.MaxTransferRate * 5;
[DataField]
public float OutputPressure = Atmospherics.MaxOutputPressure * 3;
/// <summary>
/// Max/min temperatures
/// </summary>
[DataField]
public float MaxTemp = 3000;
[DataField]
public float MinTemp = Atmospherics.T20C;
/// <summary>
/// Health of the turbine
/// </summary>
[DataField, AutoNetworkedField]
public int BladeHealth = 15;
/// <summary>
/// Maximum health of the turbine
/// </summary>
[DataField, AutoNetworkedField]
public int BladeHealthMax = 15;
/// <summary>
/// If the turbine is functional or not
/// </summary>
[DataField, ViewVariables, AutoNetworkedField]
public bool Ruined = false;
/// <summary>
/// Flag indicating the turbine is sparking
/// </summary>
[ViewVariables]
public bool IsSparking = false;
/// <summary>
/// Flag indicating the turbine is smoking
/// </summary>
[ViewVariables]
public bool IsSmoking = false;
/// <summary>
/// Flag for indicating that energy available is less than needed to turn the turbine
/// </summary>
[ViewVariables]
public bool Stalling = false;
/// <summary>
/// Flag for RPM being > BestRPM*1.2
/// </summary>
[ViewVariables]
public bool Overspeed = false;
/// <summary>
/// Flag for gas temperature being > MaxTemp - 500
/// </summary>
[ViewVariables]
public bool Overtemp = false;
/// <summary>
/// Flag for gas temperature being < MinTemp
/// </summary>
[ViewVariables]
public bool Undertemp = false;
/// <summary>
/// Adjustment for power generation
/// </summary>
[DataField]
public float PowerMultiplier = 1;
[ViewVariables, AutoNetworkedField]
public EntityUid? AlarmAudioOvertemp;
[ViewVariables, AutoNetworkedField]
public EntityUid? AlarmAudioUnderspeed;
/// <summary>
/// Length of repair do-after, in seconds
/// </summary>
[DataField]
public float RepairDelay = 5;
/// <summary>
/// Amount of fuel consumed for repair
/// </summary>
[DataField]
public float RepairFuelCost = 15;
/// <summary>
/// Tool capability needed to repair
/// </summary>
[DataField]
public ProtoId<ToolQualityPrototype> RepairTool = "Welding";
/// <summary>
/// The blade currently installed in the turbine
/// </summary>
[ViewVariables, AutoNetworkedField]
public EntityUid? CurrentBlade;
/// <summary>
/// The stator currently installed in the turbine
/// </summary>
[ViewVariables, AutoNetworkedField]
public EntityUid? CurrentStator;
#region Pipe Connections
/// <summary>
/// Name of the pipe node
/// </summary>
[DataField]
public string PipeName { get; set; } = "pipe";
/// <summary>
/// Inlet entity
/// </summary>
[ViewVariables]
public EntityUid? InletEnt;
/// <summary>
/// Position of the inlet entity
/// </summary>
[DataField]
public Vector2 InletPos = new(-1, -1);
/// <summary>
/// Rotation of the inlet entity, in degrees
/// </summary>
[DataField]
public float InletRot = -90;
/// <summary>
/// Outlet entity
/// </summary>
[ViewVariables]
public EntityUid? OutletEnt;
/// <summary>
/// Position of the outlet entity
/// </summary>
[DataField]
public Vector2 OutletPos = new(1, -1);
/// <summary>
/// Rotation of the outlet entity, in degrees
/// </summary>
[DataField]
public float OutletRot = 90;
/// <summary>
/// Name of the prototype of the arrows that indicate flow on inspect
/// </summary>
[DataField]
public EntProtoId ArrowPrototype = "TurbineFlowArrow";
/// <summary>
/// Name of the prototype of the pipes the turbine uses to connect to the pipe network
/// </summary>
[DataField]
public EntProtoId PipePrototype = "TurbineGasPipe";
#endregion
#region Device Network
/// <summary>
/// The proto ID of the "Speed: High" source port
/// </summary>
[DataField("speedHighPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
public string SpeedHighPort = "TurbineSpeedHigh";
/// <summary>
/// The proto ID of the "Speed: Low" source port
/// </summary>
[DataField("speedLowPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
public string SpeedLowPort = "TurbineSpeedLow";
/// <summary>
/// The proto ID of the "Turbine Data" source port
/// </summary>
[DataField("turbineDataPort", customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
public string TurbineDataPort = "GasTurbineDataSender";
/// <summary>
/// The proto ID of the "Increase Stator Load" sink port
/// </summary>
[DataField("statorLoadIncreasePort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string StatorLoadIncreasePort = "IncreaseStatorLoad";
/// <summary>
/// The proto ID of the "Decrease Stator Load" sink port
/// </summary>
[DataField("statorLoadDecreasePort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
public string StatorLoadDecreasePort = "DecreaseStatorLoad";
/// <summary>
/// The signal state of the increase stator load port
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SignalState IncreasePortState = SignalState.Low;
/// <summary>
/// The signal state of the decrease stator load port
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SignalState DecreasePortState = SignalState.Low;
#endregion
#region Debug
[ViewVariables(VVAccess.ReadOnly)]
public bool HasPipes = false;
[ViewVariables(VVAccess.ReadOnly)]
public float SupplierMaxSupply = 0;
[ViewVariables(VVAccess.ReadOnly)]
public float SupplierLastSupply = 0;
[ViewVariables(VVAccess.ReadOnly)]
public float LastVolumeTransfer = 0;
#endregion
}
@@ -0,0 +1,9 @@
- files: ["turbine_room.ogg"]
license: "CC-BY-3.0"
copyright: "Created by Sea Fury, clipped and converted Mono and .ogg by Jhrushbe"
source: https://freesound.org/people/Sea%20Fury/sounds/54638/
- files: ["electrical_hum.ogg"]
license: "CC-BY-4.0"
copyright: "Created by Zabuhailo, clipped and converted Mono and .ogg by Jhrushbe"
source: https://freesound.org/people/Zabuhailo/sounds/167673/
Binary file not shown.
@@ -0,0 +1,34 @@
- files: ["alarm_beep.ogg"]
license: "CC0-1.0"
copyright: "Created by JW_Audio, clipped and converted Mono and .ogg by Jhrushbe"
source: https://freesound.org/people/JW_Audio/sounds/816661/
- files: ["alarm_buzzer.ogg"]
license: "CC-BY-4.0"
copyright: "Created by NoahBangs, clipped and converted Mono and .ogg by Jhrushbe"
source: https://freesound.org/people/NoahBangs/sounds/587950/
- files: ["meltdown_siren.ogg"]
license: "CC-BY-NC-SA-3.0"
copyright: "Taken from Goonstation"
source: https://github.com/goonstation/goonstation/blob/ff86b044/sound/machines/meltdown_siren.ogg
- files: ["reactor_alarm_1.ogg"]
license: "CC-BY-4.0"
copyright: "Created by Tecc_Source, clipped and converted Mono by Jhrushbe"
source: https://freesound.org/people/Tecc_Source/sounds/746816/
- files: ["reactor_alarm_2.ogg"]
license: "CC-BY-4.0"
copyright: "Created by Tecc_Source, clipped and converted Mono by Jhrushbe"
source: https://freesound.org/people/Tecc_Source/sounds/746818/
- files: ["reactor_alarm_3.ogg"]
license: "CC-BY-4.0"
copyright: "Created by Tecc_Source, converted Mono by Jhrushbe"
source: https://freesound.org/people/Tecc_Source/sounds/746819/
- files: ["relay_click.ogg"]
license: "CC0-1.0"
copyright: "Created by bassmosphere, remixed and converted Mono and .ogg by Jhrushbe"
source: https://freesound.org/people/bassmosphere/sounds/384698/
@@ -0,0 +1,4 @@
- files: ["orange.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Made by Conflee"
source: "https://bsky.app/profile/conflee.bsky.social"
Binary file not shown.
@@ -0,0 +1,4 @@
nuclear-centrifuge-insert-item = {CAPITALIZE(THE($user))} inserted {THE($item)} into {THE($machine)}.
nuclear-centrifuge-wrong-item = You can't put {THE($item)} in here, it doesn't fit.
nuclear-centrifuge-unfit-item = {THE($item)} isn't ready for reprocessing.
@@ -0,0 +1,47 @@
### Popups
reactor-smoke-start = The {$owner} begins to smoke!
reactor-smoke-stop = The {$owner} stops smoking.
reactor-fire-start = The {$owner} begins to burn!
reactor-fire-stop = The {$owner} stops burning.
reactor-unanchor-melted = You cannot unanchor the nuclear reactor, it's melted into the hull!
reactor-unanchor-warning = You cannot unanchor the nuclear reactor while it's not empty or hotter than 80C!
reactor-anchor-warning = Invalid anchor position.
### Messages
reactor-smoke-start-message = ALERT: The {$owner} has reached a dangerous temperature: {$temperature}K. Intervene immediately to prevent meltdown.
reactor-smoke-stop-message = The {$owner} has cooled below dangerous temperature. Have a nice day.
reactor-fire-start-message = ALERT: The {$owner} has reached CRITICAL temperature: {$temperature}K. MELTDOWN IMMINENT.
reactor-fire-stop-message = The {$owner} has cooled below critical temperature. Meltdown averted.
reactor-temperature-dangerous-message = The {$owner} is at dangerous temperature: {$temperature}K.
reactor-temperature-critical-message = The {$owner} is at critical temperature: {$temperature}K.
reactor-temperature-cooling-message = The {$owner} is cooling: {$temperature}K.
reactor-melting-announcement = A nuclear reactor aboard the station is beginning to meltdown. Evacuation of the surrounding area is advised.
reactor-melting-announcement-sender = Nuclear Emergency
reactor-meltdown-announcement = A nuclear reactor aboard the station has catastrophically overloaded. Radioactive debris, nuclear fallout, and coolant fires are likely. Immediate evacuation of the surrounding area is strongly advised.
reactor-meltdown-announcement-sender = Nuclear Meltdown
### UI
comp-nuclear-reactor-ui-locked = Locked
comp-nuclear-reactor-ui-insert-button = Insert
comp-nuclear-reactor-ui-remove-button = Remove
comp-nuclear-reactor-ui-eject-button = Eject
comp-nuclear-reactor-ui-view-change = Change View
comp-nuclear-reactor-ui-view-temp = Temperature View
comp-nuclear-reactor-ui-view-neutron = Neutron View
comp-nuclear-reactor-ui-view-target = Target View
comp-nuclear-reactor-ui-view-fuel = Fuel View
comp-nuclear-reactor-ui-status-panel = Reactor Status
comp-nuclear-reactor-ui-reactor-temp = Temperature
comp-nuclear-reactor-ui-reactor-rads = Radiation
comp-nuclear-reactor-ui-reactor-therm = Thermal Power
comp-nuclear-reactor-ui-reactor-control = Control Rods
comp-nuclear-reactor-ui-therm-format = { POWERWATTS($power) }t
comp-nuclear-reactor-ui-footer-left = Danger: high radiation.
comp-nuclear-reactor-ui-footer-right = 0.8 REV 3
@@ -0,0 +1,16 @@
reactor-part-nrad-0 = It's glowing slightly blue.
reactor-part-nrad-1 = It's glowing somewhat blue.
reactor-part-nrad-2 = It's glowing blue.
reactor-part-nrad-3 = It's brightly glowing blue.
reactor-part-nrad-4 = It's brilliantly glowing blue.
reactor-part-nrad-5 = It's blindingly glowing blue.
reactor-part-rad-0 = It's slightly radioactive.
reactor-part-rad-1 = It's somewhat radioactive.
reactor-part-rad-2 = It's radioactive.
reactor-part-rad-3 = It's very radioactive.
reactor-part-rad-4 = It's extremely radioactive.
reactor-part-rad-5 = It's impossibly radioactive.
reactor-part-hot = [color=yellow]It's hot to the touch.[/color]
reactor-part-burning = [color=red]Heat distorts the air around it.[/color]
@@ -0,0 +1,77 @@
### Examine
gas-turbine-examine-stator-null = It seems to be missing a stator.
gas-turbine-examine-stator = It has a stator.
gas-turbine-examine-blade-null = It seems to be missing a turbine blade.
gas-turbine-examine-blade = It has a turbine blade.
turbine-spinning-0 = The blades are not spinning.
turbine-spinning-1 = The blades are turning slowly.
turbine-spinning-2 = The blades are spinning.
turbine-spinning-3 = The blades are spinning quickly.
turbine-spinning-4 = [color=red]The blades are spinning out of control![/color]
turbine-damaged-0 = It appears to be in good condition.[/color]
turbine-damaged-1 = The turbine looks a bit scuffed.[/color]
turbine-damaged-2 = [color=yellow]The turbine looks badly damaged.[/color]
turbine-damaged-3 = [color=orange]It's critically damaged![/color]
turbine-ruined = [color=red]It's completely broken![/color]
### Popups
# Shown when an event occurs
turbine-overheat = {$owner} triggers the emergency overheat dump valve!
turbine-explode = The {$owner} tears itself apart!
# Shown when damage occurs
turbine-spark = The {$owner} starts sparking!
turbine-spark-stop = The {$owner} stops sparking.
turbine-smoke = The {$owner} begins to smoke!
turbine-smoke-stop = The {$owner} stops smoking.
# Shown during repairs
gas-turbine-repair-fail-blade = You need to replace the turbine blade before this can be repaired.
gas-turbine-repair-fail-stator = You need to replace the stator before this can be repaired.
turbine-repair-ruined = You repair the {$target}'s casing with the {$tool}.
turbine-repair = You repair some of the damage to the {$target} using the {$tool}.
turbine-no-damage = There is no damage to repair on the {$target} using the {$tool}.
turbine-show-damage = BladeHealth {$health}, BladeHealthMax {$healthMax}.
# Anchoring warnings
turbine-unanchor-warning = You cannot unanchor the gas turbine while the turbine is spinning!
turbine-anchor-warning = Invalid anchor position.
gas-turbine-eject-fail-speed = You cannot remove turbine parts while the turbine is spinning!
gas-turbine-insert-fail-speed = You cannot insert turbine parts while the turbine is spinning!
### UI
# Shown when using the UI
comp-turbine-ui-tab-main = Controls
comp-turbine-ui-tab-parts = Parts
comp-turbine-ui-rpm = RPM
comp-turbine-ui-overspeed = OVERSPEED
comp-turbine-ui-overtemp = OVERTEMP
comp-turbine-ui-stalling = STALLING
comp-turbine-ui-undertemp = UNDERTEMP
comp-turbine-ui-flow-rate = Flow Rate
comp-turbine-ui-stator-load = Stator Load
comp-turbine-ui-blade = Turbine Blade
comp-turbine-ui-blade-integrity = Integrity
comp-turbine-ui-blade-stress = Stress
comp-turbine-ui-stator = Turbine Stator
comp-turbine-ui-stator-potential = Potential
comp-turbine-ui-stator-supply = Supply
comp-turbine-ui-power = { POWERWATTS($power) }
comp-turbine-ui-locked-message = Controls locked.
comp-turbine-ui-footer-left = Danger: fast-moving machinery.
comp-turbine-ui-footer-right = 2.0 REV 1
@@ -0,0 +1,6 @@
ghost-role-information-plutonium-rod-name = Plutonium Fuel Rod
ghost-role-information-plutonium-rod-description = Does not want to become power.
ghost-role-information-plutonium-rod-rules = You are a [color={role-type-free-agent-color}][bold]{ghost-role-information-plutonium-rod-name}[/bold][/color]. Your one goal is not to be put into the nuclear reactor or centrifuge, because if you are, you [color=red]die[/color].
You don't remember any of your previous life, and you don't remember anything you learned as a ghost.
You [color=red]don't[/color] remember knowledge about the game in general, such as how to cook, how to use objects, etc.
You are absolutely [color=red]NOT[/color] allowed to remember, say, the name, appearance, etc. of your previous character.
@@ -0,0 +1,2 @@
guide-entry-nuclear-reactor = Nuclear Reactor
guide-entry-nuclear-materials = Material Effects
@@ -0,0 +1,17 @@
signal-port-name-nuclear-reactor-data-receiver = Reactor Monitor
signal-port-description-nuclear-reactor-data-receiver = Receiving info about a nuclear reactor.
signal-port-name-nuclear-reactor-receiver-insert = Control Rods In
signal-port-description-nuclear-reactor-receiver-insert = Increases the insertion level of the control rods.
signal-port-name-nuclear-reactor-receiver-retract = Control Rods Out
signal-port-description-nuclear-reactor-receiver-retract = Decreases the insertion level of the control rods.
signal-port-name-gas-turbine-data-receiver = Turbine Monitor
signal-port-description-gas-turbine-data-receiver = Receiving info about a gas turbine.
signal-port-name-gas-turbine-receiver-increase = Increase Load
signal-port-description-gas-turbine-receiver-increase = Increases the stator load of the turbine.
signal-port-name-gas-turbine-receiver-decrease = Decrease Load
signal-port-description-gas-turbine-receiver-decrease = Decreases the stator load of the turbine.
@@ -0,0 +1,11 @@
signal-port-name-nuclear-reactor-data-sender = Nuclear Reactor
signal-port-description-nuclear-reactor-data-sender = Sending info about a nuclear reactor to a monitor.
signal-port-name-gas-turbine-data-sender = Gas Turbine
signal-port-description-gas-turbine-data-sender = Sending info about a gas turbine to a monitor.
signal-port-name-gas-turbine-speed-high = High Speed
signal-port-description-gas-turbine-speed-high = Speed is higher than optimal.
signal-port-name-gas-turbine-speed-low = Low Speed
signal-port-description-gas-turbine-speed-low = Speed is lower than optimal.
@@ -0,0 +1,3 @@
materials-cerenkite = cerenkite
materials-plutonium = plutonium
materials-bohrum = bohrum
@@ -0,0 +1,3 @@
# Thief Collection
steal-target-groups-reactorfuelrod = reactor fuel rod
@@ -0,0 +1,2 @@
research-technology-nuclear-power = Nuclear Power
research-technology-nuclear-recycling = Nuclear Waste Recycling
@@ -0,0 +1 @@
stack-plutonium = plutonium
@@ -0,0 +1,3 @@
alert-level-orange = Orange
alert-level-orange-announcement = There is a critical station-wide structural or atmospheric threat and recovery is unlikely. Engineering staff are advised to minimize hazards and secure the Evacuation Dock. Crewmembers are advised to stay away from hazardous areas, and prepare for Evacuation. MAYDAY signals have been activated
alert-level-orange-instructions = Avoid hazards and prepare for Evacuation.
@@ -43,6 +43,17 @@
emergencyLightColor: Red
forceEnableEmergencyLights: true
shuttleTime: 600 #No reduction in time as we don't have swiping for red alert like in /tg/. Shuttle times are intended to create friction, so having a way to brainlessly bypass that would be dumb.
# Begin Starlight Additions
orange:
announcement: alert-level-orange-announcement
selectable: true
sound:
path: /Audio/_Starlight/misc/orange.ogg
color: "#ff8119"
emergencyLightColor: "#ff8119"
forceEnableEmergencyLights: true
shuttleTime: 480
# End Starlight Additions
gamma:
announcement: alert-level-gamma-announcement
selectable: false
@@ -114,6 +114,7 @@
- id: DrinkVodkaBottleFull
- id: DrinkShotGlass
amount: 2
- id: RadiationBlockingProjector # Far Horizons
- type: entity
parent: CratePlastic
@@ -210,6 +210,7 @@
- id: LunchboxEngineeringFilledRandom # DeltaV - Lunchboxes!
prob: 0.3
- id: HolotapeProjector # DeltaV - engineering tape
- id: PillCanisterPotassiumIodide # Far Horizons / Nuclear Reactor Port
- type: entityTable
id: FillEngineerHardsuit
@@ -237,9 +238,11 @@
- id: ClothingOuterSuitRad
amount: 2
- id: GeigerCounter
amount: 2
- id: PillCanisterPotassiumIodide
prob: 0.75
amount: !type:ConstantNumberSelector
value: 2
- id: PillCanisterPotassiumIodide # Far Horizons / Nuclear Reactor Port Start
amount: !type:ConstantNumberSelector
value: 2 # Far Horizons / Nuclear Reactor Port End
- type: entity
parent: LockerEvacRepair
@@ -67,7 +67,7 @@
boardName: wires-board-name-autolathe
layoutId: Fabicator
alwaysRandomize: true
- type: StationAiWhitelist
- type: StationAiWhitelist
- type: MaterialStorageMagnetPickup
magnetEnabled: True
range: 0.30
@@ -297,6 +297,7 @@
- MechBoards
- ShuttleBoards
- EngineeringBoardsGoob #Goob Factorio port
- ReactorCircuitboards # FH edit
- type: EmagLatheRecipes
emagDynamicPacks:
- SecurityBoards
@@ -320,6 +320,7 @@
- PortableGenerator
- AME
- SingularityTeslaEngine
- NuclearReactor # FH edit
- TEG
- RTG
- SolarPanels
@@ -84,6 +84,7 @@
MailStealCollectionObjective: 1 #cargo
IDCardsStealCollectionObjective: 1
LAMPStealCollectionObjective: 2 #only for moth
ReactorFuelRodStealCollectionObjective: 1 #Far Horizons/Nuclear Reactor Port
- type: weightedRandom
id: ThiefObjectiveGroupItem
@@ -5,6 +5,12 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: glass }
color: "#a8ccd7"
price: 0.15 # $15 for one unit
properties: # FH edit
electrical: 3
thermal: 3
hard: 3
density: 2
chemical: 5
- type: material
id: ReinforcedGlass
@@ -13,6 +19,13 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rglass }
color: "#549bb0"
price: 0.225 # 1-0.5 mix of glass and metal.
# Interpolated properties
properties: # FH edit
electrical: 3.66
thermal: 4
hard: 3
density: 2.66
chemical: 5.33
- type: material
id: PlasmaGlass
@@ -21,6 +34,12 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: pglass }
color: "#b35989"
price: 0.50 # 1-1 mix of plasma and glass.
properties: # FH edit
electrical: 3
thermal: 3
hard: 7
density: 3
chemical: 5
- type: material
id: ReinforcedPlasmaGlass
@@ -29,6 +48,13 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: rpglass }
color: "#8c4069"
price: 0.575 # 1-1-0.5 mix of plasma, glass, and metal.
# Interpolated properties
properties: # FH edit
electrical: 3.66
thermal: 4
hard: 5.66
density: 3.33
chemical: 5.33
- type: material
id: BrassGlass
@@ -37,6 +63,13 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: cglass }
color: "#9b6f07"
price: 0.225 # average of glass and brass.
# Interpolated properties
properties: # FH edit
electrical: 4
thermal: 4.5
hard: 3
density: 3
chemical: 5.5
- type: material
id: UraniumGlass
@@ -45,6 +78,15 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: uglass }
color: "#3cab38"
price: 1.0 # 1-1 mix of uranium and glass.
# Interpolated properties
properties: # FH edit
electrical: 3.5
thermal: 3
hard: 4
density: 4.5
chemical: 5.5
radioactive: 2 # Making something out of uranium is radioactive... imagine that
n_radioactive: 1.5
- type: material
id: ReinforcedUraniumGlass
@@ -53,3 +95,12 @@
icon: { sprite: Objects/Materials/Sheets/glass.rsi, state: ruglass }
color: "#2d872a"
price: 1.075 # 2-2-1 mix of uranium, glass, and metal.
# Interpolated properties
properties: # FH edit
electrical: 3.8
thermal: 3.6
hard: 3.8
density: 3.3
chemical: 5.6
radioactive: 1.6
n_radioactive: 1.2
@@ -4,6 +4,12 @@
name: materials-steel
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: steel }
price: 0.15 # $15 for 1 unit
properties: # FH edit
electrical: 5
thermal: 6
hard: 3
density: 4
chemical: 6
- type: material
id: Gold
@@ -13,6 +19,13 @@
icon: { sprite: Objects/Materials/ingots.rsi, state: gold }
color: "#FFD700"
price: 0.75 # $75 for 1 unit
properties: # FH edit
electrical: 7
thermal: 7
hard: 2
density: 6
reflective: 6
chemical: 6
- type: material
id: Silver
@@ -22,6 +35,13 @@
icon: { sprite: Objects/Materials/ingots.rsi, state: silver }
color: "#C0C0C0"
price: 0.60 # $60 for 1 unit
properties: # FH edit
electrical: 6
thermal: 6
hard: 2
density: 4
reflective: 6
chemical: 6
- type: material
id: Brass
@@ -30,6 +50,12 @@
icon: { sprite: _DV/Objects/Materials/Sheets/metal.rsi, state: brass } # DeltaV - new brass textures
color: "#b18b25"
price: 0.30 # $30 for 1 unit
properties: # FH edit
electrical: 6
thermal: 7
hard: 2
density: 5
chemical: 7
- type: material
id: Plasteel
@@ -38,3 +64,9 @@
icon: { sprite: Objects/Materials/Sheets/metal.rsi, state: plasteel }
color: "#696969" #Okay, this is epic
price: 0.5 # 1-1 mix of plasma and steel.
properties: # FH edit
electrical: 5
thermal: 6
hard: 3
density: 7
chemical: 6
@@ -0,0 +1,59 @@
- type: cargoProduct
id: NuclearReactorFlatpackPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/cargo_icons.rsi
state: reactor
product: NuclearReactorFlatpack
cost: 15000
category: cargoproduct-category-name-engineering
group: market
- type: cargoProduct
id: GasTurbineFlatpackPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/cargo_icons.rsi
state: turbine
product: GasTurbineFlatpack
cost: 10000
category: cargoproduct-category-name-engineering
group: market
- type: cargoProduct
id: CerenkiteFuelRodPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
product: CrateCerenkiteFuelRod
cost: 4000
category: cargoproduct-category-name-engineering
group: market
- type: cargoProduct
id: BohrumControlRodPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
product: CrateBohrumControlRod
cost: 2000
category: cargoproduct-category-name-engineering
group: market
- type: cargoProduct
id: SteelGasChannelPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
product: CrateSteelGasChannel
cost: 1000
category: cargoproduct-category-name-engineering
group: market
- type: cargoProduct
id: SteelHeatExhangerPurchase
icon:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
product: CrateSteelHeatExhanger
cost: 1000
category: cargoproduct-category-name-engineering
group: market
@@ -0,0 +1,43 @@
- type: entity
id: CrateCerenkiteFuelRod
parent: CrateEngineeringSecure
name: fuel rod crate
description: 3 cerenkite fuel rods, for fuelling a nuclear reactor.
components:
- type: StorageFill
contents:
- id: CerenkiteReactorFuelRod
amount: 3
- type: entity
id: CrateBohrumControlRod
parent: CrateEngineeringSecure
name: control rod crate
description: 2 bohrum control rods for a nuclear reactor.
components:
- type: StorageFill
contents:
- id: BohrumReactorControlRod
amount: 2
- type: entity
id: CrateSteelGasChannel
parent: CrateEngineeringSecure
name: gas channel rod crate
description: 4 steel gas channel rods for a nuclear reactor.
components:
- type: StorageFill
contents:
- id: SteelReactorGasChannel
amount: 4
- type: entity
id: CrateSteelHeatExhanger
parent: CrateEngineeringSecure
name: heat exchanger rod crate
description: 4 steel heat exchanger rods for a nuclear reactor.
components:
- type: StorageFill
contents:
- id: SteelReactorHeatExchanger
amount: 4
@@ -0,0 +1,15 @@
- type: damageModifierSet
id: StructuralReactorCasing # StructuralMetallicStrong(er), it's going to take more than a mere baseball bat to do any real damage
coefficients:
Blunt: 0.1 # you
Slash: 0.1 # aint
Piercing: 0.1 # gonna
Shock: 0.2 # do
Heat: 0.1 # shit
Structural: 0.25
flatReductions:
Blunt: 10
Slash: 10
Piercing: 10
Heat: 10
Structural: 10
@@ -0,0 +1,31 @@
# Nuclear Reactor
- type: sinkPort
id: InsertControlRods
name: signal-port-name-nuclear-reactor-receiver-insert
description: signal-port-description-nuclear-reactor-receiver-insert
- type: sinkPort
id: RetractControlRods
name: signal-port-name-nuclear-reactor-receiver-retract
description: signal-port-description-nuclear-reactor-receiver-retract
- type: sinkPort
id: NuclearReactorDataReceiver
name: signal-port-name-nuclear-reactor-data-receiver
description: signal-port-description-nuclear-reactor-data-receiver
# Gas Turbine
- type: sinkPort
id: IncreaseStatorLoad
name: signal-port-name-gas-turbine-receiver-increase
description: signal-port-description-gas-turbine-receiver-increase
- type: sinkPort
id: DecreaseStatorLoad
name: signal-port-name-gas-turbine-receiver-decrease
description: signal-port-description-gas-turbine-receiver-decrease
- type: sinkPort
id: GasTurbineDataReceiver
name: signal-port-name-gas-turbine-data-receiver
description: signal-port-description-gas-turbine-data-receiver
@@ -0,0 +1,23 @@
# Nuclear Reactor
- type: sourcePort
id: NuclearReactorDataSender
name: signal-port-name-nuclear-reactor-data-sender
description: signal-port-description-nuclear-reactor-data-sender
defaultLinks: [ NuclearReactorDataReceiver ]
# Gas Turbine
- type: sourcePort
id: GasTurbineDataSender
name: signal-port-name-gas-turbine-data-sender
description: signal-port-description-gas-turbine-data-sender
defaultLinks: [ GasTurbineDataReceiver ]
- type: sourcePort
id: TurbineSpeedHigh
name: signal-port-name-gas-turbine-speed-high
description: signal-port-description-gas-turbine-speed-high
- type: sourcePort
id: TurbineSpeedLow
name: signal-port-name-gas-turbine-speed-low
description: signal-port-description-gas-turbine-speed-low
@@ -0,0 +1,27 @@
- type: entity
id: NuclearFabricatorMachineCircuitboard
parent: BaseMachineCircuitboard
name: nuclear fabricator machine board
description: A machine printed circuit board for a nuclear fabricator.
components:
- type: Sprite
state: engineering
- type: MachineBoard
prototype: NuclearFabricator
stackRequirements:
Manipulator: 5
Glass: 3
- type: entity
id: NuclearCentrifugeMachineCircuitboard
parent: BaseMachineCircuitboard
name: nuclear centrifuge machine board
description: A machine printed circuit board for a nuclear centrifuge.
components:
- type: Sprite
state: engineering
- type: MachineBoard
prototype: NuclearCentrifuge
stackRequirements:
Manipulator: 5
Plasteel: 5
@@ -0,0 +1,21 @@
- type: entity
parent: BaseComputerCircuitboard
id: NuclearReactorMonitorComputerCircuitboard
name: nuclear reactor monitor computer board
description: A computer printed circuit board for a nuclear reactor monitor.
components:
- type: Sprite
state: cpu_engineering
- type: ComputerBoard
prototype: NuclearReactorMonitor
- type: entity
parent: BaseComputerCircuitboard
id: GasTurbineMonitorComputerCircuitboard
name: gas turbine monitor computer board
description: A computer printed circuit board for a gas turbine monitor.
components:
- type: Sprite
state: cpu_engineering
- type: ComputerBoard
prototype: GasTurbineMonitor
@@ -0,0 +1,25 @@
- type: entity
parent: CrateEngineering
id: NuclearReactorFlatpack
name: nuclear reactor flatpack
description: A flatpack used for constructing a nuclear reactor. Parts sold separately.
components:
- type: Sprite
sprite: Objects/Devices/flatpack.rsi
layers:
- state: large
- type: Flatpack
entity: NuclearReactorEmpty
- type: entity
parent: CrateEngineering
id: GasTurbineFlatpack
name: gas turbine flatpack
description: A flatpack used for constructing a gas turbine.
components:
- type: Sprite
sprite: Objects/Devices/flatpack.rsi
layers:
- state: large
- type: Flatpack
entity: Turbine
@@ -0,0 +1,22 @@
- type: entity
parent: Holoprojector
id: RadiationBlockingProjector
name: radiation barrier projector
description: Stop the deadly radiation from a delamination or meltdown, at least for a little while.
components:
- type: HolosignProjector
signProto: HolosignRadiationBlocking
chargeUse: 60
- type: Sprite
sprite: _FarHorizons/Objects/Devices/Holoprojectors/radiation.rsi
state: icon
- type: Tag
tags:
- HolofanProjector
# - type: StaticPrice
# price: 50
# - type: GuideHelp
# guides:
# - AtmosTools
# - Fires
# - Spacing
@@ -0,0 +1,52 @@
- type: entity
parent: IngotBase
id: IngotPlutonium
name: plutonium bar
suffix: Full
components:
- type: Material
- type: PhysicalComposition
materialComposition:
Plutonium: 100
- type: Stack
stackType: Plutonium
baseLayer: base
layerStates:
- iron # Why iron? Because I haven't gotten a spriter to make better
- iron_2
- iron_3
- type: Sprite
state: iron_3
layers:
- state: iron_3
map: ["base"]
- type: Item
heldPrefix: plutonium
- type: Appearance
# - type: Extractable
# grindableSolutionName: gold
# - type: SolutionContainerManager
# solutions:
# gold:
# reagents:
# - ReagentId: Silver
# Quantity: 10
- type: RadiationSource
intensity: 0.08
slope: 3
- type: PointLight
radius: 1.2
energy: 0.8
castShadows: false
color: "#22bbff"
- type: entity
parent: IngotPlutonium
id: IngotPlutonium1
name: plutonium bar
suffix: Single
components:
- type: Sprite
state: iron
- type: Stack
count: 1
@@ -0,0 +1,20 @@
- type: entity
id: HolosignRadiationBlocking
parent: HolosignWetFloor
name: holographic radiation barrier
description: A barrier of hard light that blocks light and radiation, but nothing else.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Holo/radiation.rsi
state: icon
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-0.5,-0.5,0.5,0.5"
- type: TimedDespawn
lifetime: 180
- type: Occluder
- type: RadiationBlocker
resistance: 50
@@ -0,0 +1,87 @@
- type: entity
parent: BaseComputerAiAccess
id: NuclearReactorMonitor
name: nuclear reactor monitor
description: A device that monitors the state of a connected nuclear reactor.
components:
- type: ApcPowerReceiver
powerLoad: 500
- type: PointLight
radius: 1.5
energy: 1.6
color: "#c9c042"
- type: Sprite
sprite: Structures/Machines/computers.rsi
layers:
- map: ["computerLayerBody"]
state: computer
- map: ["computerLayerScreen"]
state: power_monitor
- type: NuclearReactorMonitor
- type: ActivatableUI
key: enum.NuclearReactorUiKey.Key
- type: UserInterface
interfaces:
enum.NuclearReactorUiKey.Key:
type: NuclearReactorBoundUserInterface
- type: AccessReader # You got a permit for that meltdown?
access: [["Engineering"]]
- type: Lock
unlockOnClick: false
- type: DeviceList
- type: DeviceNetwork
deviceNetId: Wireless
- type: DeviceLinkSink
ports:
- NuclearReactorDataReceiver
- type: Computer
board: NuclearReactorMonitorComputerCircuitboard
- type: ContainerContainer
containers:
board: !type:Container
machine_board: !type:Container
machine_parts: !type:Container
- type: entity
parent: BaseComputerAiAccess
id: GasTurbineMonitor
name: gas turbine monitor
description: A device that monitors the state of a connected gas turbine.
components:
- type: ApcPowerReceiver
powerLoad: 500
- type: PointLight
radius: 1.5
energy: 1.6
color: "#c9c042"
- type: Sprite
sprite: Structures/Machines/computers.rsi
layers:
- map: ["computerLayerBody"]
state: computer
- map: ["computerLayerScreen"]
state: turbinecomp
- type: GasTurbineMonitor
- type: ActivatableUI
key: enum.TurbineUiKey.Key
- type: UserInterface
interfaces:
enum.TurbineUiKey.Key:
type: TurbineBoundUserInterface
- type: AccessReader # Keeps some random tider from blowing up the turbine
access: [["Engineering"]]
- type: Lock
unlockOnClick: false
- type: DeviceList
- type: DeviceNetwork
deviceNetId: Wireless
- type: DeviceLinkSink
ports:
- GasTurbineDataReceiver
- type: Computer
board: GasTurbineMonitorComputerCircuitboard
- type: ContainerContainer
containers:
board: !type:Container
machine_board: !type:Container
machine_parts: !type:Container
@@ -0,0 +1,13 @@
- type: navMapBlip
id: NuclearReactor
scale: 2.75
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_hexagon.png"
- type: navMapBlip
id: GasTurbine
scale: 1.75
color: LightGray
texturePaths:
- "/Textures/Interface/NavMap/beveled_hexagon.png"
@@ -0,0 +1,33 @@
- type: entity
id: NuclearCentrifuge
parent: [ BaseMachinePowered, ConstructibleMachine ]
name: nuclear centrifuge
description: A large machine that can be used to separate radioactive isotopes from spent fuel.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Machines/nuclear_centrifuge.rsi
snapCardinals: true
layers:
- state: centrifuge
map: ["enum.NuclearCentrifugeVisualLayers.Processing"]
# - state: panel
# map: ["enum.WiresVisualLayers.MaintenancePanel"]
# visible: false
- state: centrifuge_onlight
map: ["enum.PowerDeviceVisualLayers.Powered"]
- type: GenericVisualizer
visuals:
enum.NuclearCentrifugeVisuals.Processing:
enum.NuclearCentrifugeVisualLayers.Processing:
True: { state: centrifuge_process }
False: { state: centrifuge }
enum.PowerDeviceVisuals.Powered:
enum.PowerDeviceVisualLayers.Powered:
True: { visible: True }
False: { visible: False }
- type: Appearance
- type: Machine
board: NuclearCentrifugeMachineCircuitboard
- type: WiresPanel
- type: WiresVisuals
- type: NuclearCentrifuge
@@ -0,0 +1,55 @@
- type: entity
id: NuclearFabricator
parent: BaseLatheLube
name: nuclear fabricator
description: It makes nuclear parts.
components:
- type: Sprite
sprite: Structures/Machines/protolathe.rsi
snapCardinals: true
layers:
- state: icon
map: ["enum.LatheVisualLayers.IsRunning"]
- state: unlit
shader: unshaded
map: ["enum.PowerDeviceVisualLayers.Powered"]
- state: inserting
map: ["enum.MaterialStorageVisualLayers.Inserting"]
- state: panel
map: ["enum.WiresVisualLayers.MaintenancePanel"]
- type: Machine
board: NuclearFabricatorMachineCircuitboard
- type: MaterialStorage
whitelist:
tags:
- Sheet
- RawMaterial
- Ingot
- type: OreSiloClient
- type: Lathe
idleState: icon
runningState: building
unlitIdleState: unlit
unlitRunningState: unlit-building
staticPacks:
- ReactorPartsBasic
dynamicPacks:
- ReactorFuelRods
- ReactorControlRods
- ReactorGasChannels
- ReactorHeatExchangers
- GasTurbineBlades
- GasTurbineStators
# - type: EmagLatheRecipes
# emagStaticPacks:
# - StarlightSecurityEmagWeaponsStatic
# - StarlightSecurityAmmoStatic
# - StarlightSecurityAmmoEmagStatic
- type: ContainerContainer
containers:
machine_board: !type:Container
machine_parts: !type:Container
blueprint: !type:Container
- type: EmptyOnMachineDeconstruct
containers:
- blueprint
@@ -0,0 +1,132 @@
- type: entity
id: BaseGasTurbinePart
parent: BaseItem
abstract: true
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/turbine_parts.rsi
state: turbine_blade
- type: Item
size: Huge
- type: MultiHandedItem
- type: GuideHelp
guides:
- NuclearReactor
- Power
# Blades
- type: entity
id: BaseGasTurbineBlade
parent: BaseGasTurbinePart
categories: [ HideSpawnMenu ]
name: gas turbine blade
description: A replacement blade for the gas turbine.
components:
- type: GasTurbineBlade
- type: Tag
tags:
- GasTurbineBlade
- type: entity
id: SteelGasTurbineBlade
parent: BaseGasTurbineBlade
name: steel gas turbine blade
components:
- type: GasTurbineBlade
material: Steel
- type: PhysicalComposition
materialComposition:
Steel: 1000
- type: entity
id: BrassGasTurbineBlade
parent: BaseGasTurbineBlade
name: brass gas turbine blade
components:
- type: GasTurbineBlade
material: Brass
- type: PhysicalComposition
materialComposition:
Brass: 1000
- type: entity
id: DiamondGasTurbineBlade
parent: BaseGasTurbineBlade
name: diamond gas turbine blade
components:
- type: GasTurbineBlade
material: Diamond
- type: PhysicalComposition
materialComposition:
Diamond: 1000
- type: entity
id: GoldGasTurbineBlade
parent: BaseGasTurbineBlade
name: gold gas turbine blade
components:
- type: GasTurbineBlade
material: Gold
- type: PhysicalComposition
materialComposition:
Gold: 1000
- type: entity
id: PlasteelGasTurbineBlade
parent: BaseGasTurbineBlade
name: plasteel gas turbine blade
components:
- type: GasTurbineBlade
material: Plasteel
- type: PhysicalComposition
materialComposition:
Plasteel: 1000
# Stators
- type: entity
id: BaseGasTurbineStator
parent: BaseGasTurbinePart
categories: [ HideSpawnMenu ]
name: gas turbine stator
description: A replacement stator for the gas turbine.
components:
- type: Sprite
state: turbine_stator
- type: GasTurbineStator
- type: Tag
tags:
- GasTurbineStator
- type: entity
id: SteelGasTurbineStator
parent: BaseGasTurbineStator
name: steel gas turbine stator
components:
- type: GasTurbineStator
material: Steel
- type: PhysicalComposition
materialComposition:
Steel: 1000
- type: entity
id: SilverGasTurbineStator
parent: BaseGasTurbineStator
name: silver gas turbine stator
components:
- type: GasTurbineStator
material: Silver
- type: PhysicalComposition
materialComposition:
Silver: 1000
- type: entity
id: GoldGasTurbineStator
parent: BaseGasTurbineStator
name: gold gas turbine stator
components:
- type: GasTurbineStator
material: Gold
- type: PhysicalComposition
materialComposition:
Gold: 1000
@@ -0,0 +1,511 @@
- type: entity
id: BaseNuclearReactor
parent: BaseStructure
abstract: true
categories: [ HideSpawnMenu ]
name: nuclear reactor
description: A nuclear reactor vessel, with slots for fuel rods and other components. Hey wait, didn't one of these explode once?
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/nuclear_reactor.rsi
layers:
- state: reactor
visible: true
map: [ "enum.ReactorVisualLayers.Sprite" ]
- state: reactor_status_active
shader: unshaded
visible: false
map: [ "enum.ReactorVisualLayers.Status" ]
- state: reactor_gas_input_lights
shader: unshaded
visible: false
map: [ "enum.ReactorVisualLayers.Input" ]
- state: reactor_gas_output_lights
shader: unshaded
visible: false
map: [ "enum.ReactorVisualLayers.Output" ]
- state: reactor_lights_warning
shader: unshaded
visible: false
map: [ "enum.ReactorVisualLayers.Lights" ]
- state: reactor_fire
visible: false
map: [ "enum.ReactorVisualLayers.Smoke" ]
- state: reactor_smoke
visible: false
map: [ "enum.ReactorVisualLayers.Fire" ]
- type: GenericVisualizer
visuals:
enum.ReactorVisuals.Sprite:
enum.ReactorVisualLayers.Sprite:
Normal: { state: reactor }
Melted: { state: reactorbroken }
enum.ReactorVisuals.Status:
enum.ReactorVisualLayers.Status:
Off: { visible: false}
Active: { state: reactor_status_active, visible: true }
Overheat: { state: reactor_status_overheat, visible: true }
Meltdown: { state: reactor_status_meltdown, visible: true }
enum.ReactorVisuals.Input:
enum.ReactorVisualLayers.Input:
True: { visible: true }
False: { visible: false }
enum.ReactorVisuals.Output:
enum.ReactorVisualLayers.Output:
True: { visible: true }
False: { visible: false }
enum.ReactorVisuals.Lights:
enum.ReactorVisualLayers.Lights:
LightsOff: { visible: false }
LightsWarning: { state: reactor_lights_warning, visible: true }
LightsMeltdown: { state: reactor_lights_meltdown, visible: true }
enum.ReactorVisuals.Smoke:
enum.ReactorVisualLayers.Smoke:
True: { visible: true }
False: { visible: false }
enum.ReactorVisuals.Fire:
enum.ReactorVisualLayers.Fire:
True: { visible: true }
False: { visible: false }
- type: Appearance
- type: InteractionOutline
- type: Rotatable
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-2.5,-2.5,2.5,2"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- HighImpassable
- type: Damageable
damageContainer: StructuralInorganic
damageModifierSet: StructuralReactorCasing
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 1000
behaviors:
- !type:SpawnEntitiesBehavior
spawn:
NuclearReactorMelted:
min: 1
max: 1
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:PlaySoundBehavior
sound:
collection: MetalBreak
- type: Anchorable
delay: 10
- type: AtmosDevice
- type: AtmosMonitoringConsoleDevice # TODO: this doesn't work?
navMapBlip: Thermoregulator
- type: RadiationSource
intensity: 0
- type: ContainerContainer
containers:
part_slot: !type:ContainerSlot {}
- type: ItemSlots
slots:
part_slot:
whitelist:
components:
- ReactorPart
- type: NuclearReactor
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: BasicDevice
- type: WirelessNetworkConnection
range: 200
- type: DeviceLinkSource
range: 1000 # DeltaV - Was 50
ports:
- NuclearReactorDataSender
- type: DeviceLinkSink
ports:
- InsertControlRods
- RetractControlRods
- type: ActivatableUI
key: enum.NuclearReactorUiKey.Key
- type: ActivatableUIRequiresAnchor
- type: UserInterface
interfaces:
enum.NuclearReactorUiKey.Key:
type: NuclearReactorBoundUserInterface
- type: GuideHelp
guides:
- NuclearReactor
- Power
# Crew nuclear reactors
- type: entity
id: NuclearReactorCrew
parent: BaseNuclearReactor
abstract: true
categories: [ HideSpawnMenu ]
components:
- type: AccessReader # You got a permit for that meltdown?
access: [["Engineering"]]
- type: Lock
unlockOnClick: false
- type: WarpPoint
location: Nuclear Reactor
blacklist:
tags:
- GhostOnlyWarp
- type: entity
id: NuclearReactorNormal
parent: NuclearReactorCrew
- type: entity
id: NuclearReactorEmpty
parent: NuclearReactorCrew
suffix: Empty
components:
- type: NuclearReactor
prefab: ReactorPrefabEmpty
- type: entity
id: NuclearReactorRandom
parent: NuclearReactorCrew
suffix: Random
components:
- type: NuclearReactor
prefab: random
- type: entity
id: NuclearReactorMeltdown
parent: NuclearReactorCrew
suffix: Meltdown
components:
- type: NuclearReactor
prefab: ReactorPrefab7x7Meltdown
- type: entity
id: NuclearReactorMelted # This is more of a decoration than anything else
parent: BaseStructure
suffix: Melted
name: nuclear reactor
description: A broken nuclear reactor vessel. It glows with heat and radiation.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/nuclear_reactor.rsi
layers:
- state: reactorbroken
- type: Rotatable
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-2.5,-2.5,2.5,2"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- HighImpassable
- type: RadiationSource
intensity: 7
- type: InteractionOutline
- type: PointLight
energy: 0.1
falloff: 2
radius: 3.5
color: "#FFAAAAFF"
# Small crew nuclear reactors
- type: entity
id: NuclearReactorSmall
parent: NuclearReactorCrew
name: small nuclear reactor
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/small_nuclear_reactor.rsi
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,1.5"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- HighImpassable
- type: NuclearReactor
thermalMass: 302400
reactorGridWidth: 5
reactorGridHeight: 5
prefab: ReactorPrefab5x5Normal
inletPos: -1, 1
inletRot: 180
outletPos: 1, 1
outletRot: 180
arrowPrototype: ReactorSmallFlowArrow
gridbounds: [ 12, 11, 0, 0 ]
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 700
behaviors:
- !type:SpawnEntitiesBehavior
spawn:
NuclearReactorSmallMelted:
min: 1
max: 1
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:PlaySoundBehavior
sound:
collection: MetalBreak
- type: entity
id: NuclearReactorSmallRandom
parent: NuclearReactorSmall
suffix: Random
components:
- type: NuclearReactor
prefab: random
- type: entity
id: NuclearReactorSmallMelted
parent: NuclearReactorMelted
suffix: Melted
name: small nuclear reactor
description: A broken nuclear reactor vessel. It glows with heat and radiation.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/small_nuclear_reactor.rsi
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,1.5"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- HighImpassable
- type: RadiationSource
intensity: 5
- type: PointLight
radius: 2.5
# Salvage nuclear reactors
- type: entity
id: NuclearReactorSalvage
parent: BaseNuclearReactor
abstract: true
categories: [ HideSpawnMenu ]
- type: entity
id: NuclearReactorNormalSalvage
parent: NuclearReactorSalvage
suffix: Salvage
- type: entity
id: NuclearReactorEmptySalvage
parent: NuclearReactorSalvage
suffix: "Empty, Salvage"
components:
- type: NuclearReactor
prefab: ReactorPrefabEmpty
- type: entity
id: NuclearReactorRandomSalvage
parent: NuclearReactorSalvage
suffix: "Random, Salvage"
components:
- type: NuclearReactor
prefab: random
- type: entity
id: NuclearReactorMeltedSalvage
parent: NuclearReactorMelted
suffix: "Melted, Salvage"
description: A nuclear reactor vessel, long since melted down. It still glows with residual heat and radiation.
components:
- type: RadiationSource
intensity: 3.5
- type: PointLight
energy: 0.025
# Small salvage nuclear reactors
- type: entity
id: NuclearReactorSmallSalvage
parent: NuclearReactorSalvage
suffix: Salvage
name: small nuclear reactor
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/small_nuclear_reactor.rsi
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,1.5"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- HighImpassable
- type: NuclearReactor
thermalMass: 302400
reactorGridWidth: 5
reactorGridHeight: 5
prefab: ReactorPrefab5x5Normal
inletPos: -1, 1
inletRot: 180
outletPos: 1, 1
outletRot: 180
arrowPrototype: ReactorSmallFlowArrow
gridbounds: [ 12, 11, 0, 0 ]
- type: entity
id: NuclearReactorSmallRandomSalvage
parent: NuclearReactorSmallSalvage
suffix: "Random, Salvage"
components:
- type: NuclearReactor
prefab: random
- type: entity
id: NuclearReactorSmallMeltedSalvage
parent: NuclearReactorSmallMelted
suffix: "Melted, Salvage"
description: A nuclear reactor vessel, long since melted down. It still glows with residual heat and radiation.
components:
- type: RadiationSource
intensity: 2.5
- type: PointLight
energy: 0.025
# Nuclear debris resultant from a meltdown
- type: entity
id: NuclearDebrisChunk
parent: BaseItem
name: nuclear debris
description: You do not see the graphite on the floor. You're in shock. Report to medical.
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/nuclear_debris.rsi
layers:
- state: graphite_damaged_0
map: [ "base" ]
- type: Item
size: Huge
- type: RandomSprite
available:
- base:
graphite_damaged_0: ""
graphite_damaged_0_mirrored: ""
graphite_damaged_1: ""
graphite_damaged_1_mirrored: ""
graphite_damaged_2: ""
graphite_damaged_2_mirrored: ""
graphite_damaged_3: ""
graphite_damaged_3_mirrored: ""
graphite_damaged_4: ""
graphite_damaged_4_mirrored: ""
- type: RadiationSource
intensity: 1.6
# Arrows to indicate flow
- type: entity
id: ReactorFlowArrow
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: Markers/teg_arrow.rsi
color: "#FFFFFFBB"
layers:
- state: arrow
rotation: 90
offset: 3, 1
- state: arrow
rotation: 90
offset: -3, -1
- type: TimedDespawn
lifetime: 2
- type: Tag
tags:
- HideContextMenu
- type: entity
id: ReactorSmallFlowArrow
parent: ReactorFlowArrow
categories: [ HideSpawnMenu ]
components:
- type: Sprite
layers:
- state: arrow
rotation: 0
offset: -1, 2
- state: arrow
rotation: 180
offset: 1, 2
# Pipes the reactor uses to connect to the pipe network
- type: entity
id: ReactorGasPipe
categories: [ HideSpawnMenu ]
components:
- type: Transform
anchored: true
- type: Anchorable
- type: NodeContainer
nodes:
pipe:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 0
pipe1:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 1
pipe2:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 2
- type: GasPipeManifold
inlets:
- pipe1
- pipe2
outlets:
- pipe
- type: PipeRestrictOverlap
- type: Tag
tags:
- HideContextMenu
- type: entity
id: ReactorAlarmEntity
categories: [ HideSpawnMenu ]
components:
- type: AmbientSound
volume: -3
range: 15
enabled: false
sound:
path: /Audio/_FarHorizons/Machines/reactor_alarm_1.ogg
@@ -0,0 +1,254 @@
- type: nuclearReactorPrefab
id: ReactorPrefabEmpty
parts:
-1,-1: BaseReactorPart # Can't be empty, so I'll put this nothing part in an inaccessible spot
- type: nuclearReactorPrefab
id: ReactorPrefab7x7Normal
parts:
2,0: SteelReactorGasChannel
2,2: SteelReactorGasChannel
2,4: SteelReactorGasChannel
2,6: SteelReactorGasChannel
4,0: SteelReactorGasChannel
4,2: SteelReactorGasChannel
4,4: SteelReactorGasChannel
4,6: SteelReactorGasChannel
2,1: SteelReactorHeatExchanger
2,3: SteelReactorHeatExchanger
2,5: SteelReactorHeatExchanger
3,0: SteelReactorHeatExchanger
3,6: SteelReactorHeatExchanger
4,1: SteelReactorHeatExchanger
4,3: SteelReactorHeatExchanger
4,5: SteelReactorHeatExchanger
3,2: BohrumReactorControlRod
3,4: BohrumReactorControlRod
- type: nuclearReactorPrefab
id: ReactorPrefab7x7Debug
parts:
2,0: SteelReactorGasChannel
2,2: SteelReactorGasChannel
2,4: SteelReactorGasChannel
2,6: SteelReactorGasChannel
4,0: SteelReactorGasChannel
4,2: SteelReactorGasChannel
4,4: SteelReactorGasChannel
4,6: SteelReactorGasChannel
2,1: SteelReactorHeatExchanger
2,3: SteelReactorHeatExchanger
2,5: SteelReactorHeatExchanger
3,0: SteelReactorHeatExchanger
3,6: SteelReactorHeatExchanger
4,1: SteelReactorHeatExchanger
4,3: SteelReactorHeatExchanger
4,5: SteelReactorHeatExchanger
3,2: BohrumReactorControlRod
3,4: BohrumReactorControlRod
3,1: CerenkiteReactorFuelRod
3,3: CerenkiteReactorFuelRod
3,5: CerenkiteReactorFuelRod
- type: nuclearReactorPrefab
id: ReactorPrefab7x7Meltdown
parts:
0,0: PlutoniumReactorFuelRod
1,0: PlutoniumReactorFuelRod
2,0: PlutoniumReactorFuelRod
3,0: PlutoniumReactorFuelRod
4,0: PlutoniumReactorFuelRod
5,0: PlutoniumReactorFuelRod
6,0: PlutoniumReactorFuelRod
0,1: PlutoniumReactorFuelRod
1,1: PlutoniumReactorFuelRod
2,1: PlutoniumReactorFuelRod
3,1: PlutoniumReactorFuelRod
4,1: PlutoniumReactorFuelRod
5,1: PlutoniumReactorFuelRod
6,1: PlutoniumReactorFuelRod
0,2: PlutoniumReactorFuelRod
1,2: PlutoniumReactorFuelRod
2,2: PlutoniumReactorFuelRod
3,2: PlutoniumReactorFuelRod
4,2: PlutoniumReactorFuelRod
5,2: PlutoniumReactorFuelRod
6,2: PlutoniumReactorFuelRod
0,3: PlutoniumReactorFuelRod
1,3: PlutoniumReactorFuelRod
2,3: PlutoniumReactorFuelRod
3,3: PlutoniumReactorFuelRod
4,3: PlutoniumReactorFuelRod
5,3: PlutoniumReactorFuelRod
6,3: PlutoniumReactorFuelRod
0,4: PlutoniumReactorFuelRod
1,4: PlutoniumReactorFuelRod
2,4: PlutoniumReactorFuelRod
3,4: PlutoniumReactorFuelRod
4,4: PlutoniumReactorFuelRod
5,4: PlutoniumReactorFuelRod
6,4: PlutoniumReactorFuelRod
0,5: PlutoniumReactorFuelRod
1,5: PlutoniumReactorFuelRod
2,5: PlutoniumReactorFuelRod
3,5: PlutoniumReactorFuelRod
4,5: PlutoniumReactorFuelRod
5,5: PlutoniumReactorFuelRod
6,5: PlutoniumReactorFuelRod
0,6: PlutoniumReactorFuelRod
1,6: PlutoniumReactorFuelRod
2,6: PlutoniumReactorFuelRod
3,6: PlutoniumReactorFuelRod
4,6: PlutoniumReactorFuelRod
5,6: PlutoniumReactorFuelRod
6,6: PlutoniumReactorFuelRod
- type: nuclearReactorPrefab
id: ReactorPrefab7x7Arachne
parts:
0,0: SteelReactorGasChannel
0,2: SteelReactorGasChannel
0,4: SteelReactorGasChannel
0,6: SteelReactorGasChannel
2,0: SteelReactorGasChannel
2,3: SteelReactorGasChannel
2,6: SteelReactorGasChannel
3,2: SteelReactorGasChannel
3,4: SteelReactorGasChannel
4,0: SteelReactorGasChannel
4,3: SteelReactorGasChannel
4,6: SteelReactorGasChannel
6,0: SteelReactorGasChannel
6,2: SteelReactorGasChannel
6,4: SteelReactorGasChannel
6,6: SteelReactorGasChannel
0,1: SteelReactorHeatExchanger
0,3: SteelReactorHeatExchanger
0,5: SteelReactorHeatExchanger
1,0: SteelReactorHeatExchanger
1,6: SteelReactorHeatExchanger
3,0: SteelReactorHeatExchanger
3,6: SteelReactorHeatExchanger
5,0: SteelReactorHeatExchanger
5,6: SteelReactorHeatExchanger
6,1: SteelReactorHeatExchanger
6,3: SteelReactorHeatExchanger
6,5: SteelReactorHeatExchanger
1,2: BohrumReactorControlRod
1,4: BohrumReactorControlRod
2,1: BohrumReactorControlRod
2,2: BohrumReactorControlRod
2,4: BohrumReactorControlRod
2,5: BohrumReactorControlRod
3,3: BohrumReactorControlRod
4,1: BohrumReactorControlRod
4,2: BohrumReactorControlRod
4,4: BohrumReactorControlRod
4,5: BohrumReactorControlRod
5,2: BohrumReactorControlRod
5,4: BohrumReactorControlRod
1,1: CerenkiteReactorFuelRod
1,3: CerenkiteReactorFuelRod
1,5: CerenkiteReactorFuelRod
3,1: CerenkiteReactorFuelRod
3,5: CerenkiteReactorFuelRod
5,1: CerenkiteReactorFuelRod
5,3: CerenkiteReactorFuelRod
5,5: CerenkiteReactorFuelRod
- type: nuclearReactorPrefab
id: ReactorPrefab7x7Lens
parts:
0,0: SteelReactorGasChannel
0,2: SteelReactorGasChannel
0,4: SteelReactorGasChannel
0,6: SteelReactorGasChannel
1,3: SteelReactorGasChannel
2,0: SteelReactorGasChannel
2,6: SteelReactorGasChannel
3,1: SteelReactorGasChannel
3,5: SteelReactorGasChannel
4,0: SteelReactorGasChannel
4,6: SteelReactorGasChannel
5,3: SteelReactorGasChannel
6,0: SteelReactorGasChannel
6,2: SteelReactorGasChannel
6,4: SteelReactorGasChannel
6,6: SteelReactorGasChannel
0,1: SteelReactorHeatExchanger
0,3: SteelReactorHeatExchanger
0,5: SteelReactorHeatExchanger
1,0: SteelReactorHeatExchanger
1,6: SteelReactorHeatExchanger
2,3: SteelReactorHeatExchanger
3,0: SteelReactorHeatExchanger
3,2: SteelReactorHeatExchanger
3,4: SteelReactorHeatExchanger
3,6: SteelReactorHeatExchanger
4,3: SteelReactorHeatExchanger
5,0: SteelReactorHeatExchanger
5,6: SteelReactorHeatExchanger
6,1: SteelReactorHeatExchanger
6,3: SteelReactorHeatExchanger
6,5: SteelReactorHeatExchanger
1,2: BohrumReactorControlRod
1,4: BohrumReactorControlRod
2,1: BohrumReactorControlRod
2,2: BohrumReactorControlRod
2,4: BohrumReactorControlRod
2,5: BohrumReactorControlRod
4,1: BohrumReactorControlRod
4,2: BohrumReactorControlRod
4,4: BohrumReactorControlRod
4,5: BohrumReactorControlRod
5,2: BohrumReactorControlRod
5,4: BohrumReactorControlRod
1,1: CerenkiteReactorFuelRod
1,5: CerenkiteReactorFuelRod
3,3: CerenkiteReactorFuelRod
5,1: CerenkiteReactorFuelRod
5,5: CerenkiteReactorFuelRod
- type: nuclearReactorPrefab
id: ReactorPrefab5x5Normal
parts:
0,0: SteelReactorGasChannel
0,2: SteelReactorGasChannel
0,4: SteelReactorGasChannel
2,0: SteelReactorGasChannel
2,4: SteelReactorGasChannel
4,0: SteelReactorGasChannel
4,2: SteelReactorGasChannel
4,4: SteelReactorGasChannel
0,1: SteelReactorHeatExchanger
0,3: SteelReactorHeatExchanger
1,0: SteelReactorHeatExchanger
1,4: SteelReactorHeatExchanger
3,0: SteelReactorHeatExchanger
3,4: SteelReactorHeatExchanger
4,1: SteelReactorHeatExchanger
4,3: SteelReactorHeatExchanger
1,2: BohrumReactorControlRod
2,1: BohrumReactorControlRod
2,2: BohrumReactorControlRod
2,3: BohrumReactorControlRod
3,2: BohrumReactorControlRod
@@ -0,0 +1,525 @@
- type: entity
id: BaseReactorPart
parent: BaseItem
categories: [ HideSpawnMenu ]
components:
- type: ReactorPart
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
- type: Item
size: Huge
- type: MultiHandedItem
- type: Tag
tags:
- ReactorPart
- type: GuideHelp
guides:
- NuclearReactor
- Power
# Fuel Rods
- type: entity
id: BaseReactorFuelRod
parent: [ BaseReactorPart, BaseEngineeringContraband ]
categories: [ HideSpawnMenu ]
name: fuel rod
description: A fuel rod for a nuclear reactor.
components:
- type: ReactorPart
rodType: 1
iconStateInserted: "fuel"
iconStateCap: "fuel_cap"
neutronCrossSection: 1.0
thermalCrossSection: 10
thermalMass: 420000 # specific heat capacity of steel (420 J/KgK) * mass of component (1000Kg)
- type: Edible # Blame Abby for making them edible
transferAmount: null
delay: 30
forceFeedDelay: 60
- type: FlavorProfile
flavors:
- metal
- electrons
- type: BadFood
- type: SolutionContainerManager
solutions:
food:
reagents:
- ReagentId: Uranium
Quantity: 800
- ReagentId: Radium
Quantity: 200
canReact: false
- type: StealTarget
stealGroup: ReactorFuelRodSteal
- type: SentienceTarget
flavorKind: station-event-random-sentience-flavor-inanimate
weight: 0.2
- type: entity
id: CerenkiteReactorFuelRod
parent: BaseReactorFuelRod
name: cerenkite fuel rod # These are your basic, semi-harmless fuel rods
components:
- type: ReactorPart
material: Cerenkite
- type: entity
id: UraniumReactorFuelRod
parent: BaseReactorFuelRod
name: uranium fuel rod
components:
- type: ReactorPart
material: Uranium
- type: entity
id: PlutoniumReactorFuelRod
parent: BaseReactorFuelRod
name: plutonium fuel rod
components:
- type: ReactorPart
material: Plutonium
- type: GhostRole
makeSentient: true
allowSpeech: true
allowMovement: false
name: ghost-role-information-plutonium-rod-name
description: ghost-role-information-plutonium-rod-description
rules: ghost-role-information-plutonium-rod-rules
mindRoles:
- MindRoleGhostRoleFreeAgentHarmless
- type: GhostTakeoverAvailable
- type: SentienceTarget
weight: 2
- type: entity
id: BananiumReactorFuelRod
parent: BaseReactorFuelRod
name: bananium fuel rod
description: A honk rod for a nuclear reactor.
components:
- type: ReactorPart
material: Bananium
- type: entity
id: PlasmaReactorFuelRod
parent: BaseReactorFuelRod
name: plasma fuel rod
components:
- type: ReactorPart
material: Plasma
- type: entity
id: UraniumGlassReactorFuelRod
parent: BaseReactorFuelRod
name: uranium glass fuel rod
components:
- type: ReactorPart
material: UraniumGlass
- type: entity
id: MeatReactorFuelRod
parent: BaseReactorFuelRod
name: meat fuel rod
description: A fuel rod fo- wait, is it alive?
components:
- type: ReactorPart
material: Meaterial
properties:
electrical: 4
hard: 1
density: 3
flammable: 3
radioactive: 5
# Control Rods
- type: entity
id: BaseReactorControlRod
parent: BaseReactorPart
categories: [ HideSpawnMenu ]
name: control rod
description: A control rod assembly for a nuclear reactor.
components:
- type: ReactorPart
rodType: 2
iconStateInserted: "control"
iconStateCap: "control_cap"
isControlRod: true
neutronCrossSection: 2.0
- type: PhysicalComposition
materialComposition:
Steel: 250
- type: entity
id: BohrumReactorControlRod
parent: BaseReactorControlRod # what the crew start with
name: bohrum control rod
components:
- type: ReactorPart
material: Bohrum
- type: PhysicalComposition
materialComposition:
Steel: 500
Plasteel: 250
- type: entity
id: SteelReactorControlRod
parent: BaseReactorControlRod
name: steel control rod
components:
- type: ReactorPart
material: Steel
- type: PhysicalComposition
materialComposition:
Steel: 1500
- type: entity
id: GoldReactorControlRod
parent: BaseReactorControlRod
name: gold control rod
components:
- type: ReactorPart
material: Gold
- type: PhysicalComposition
materialComposition:
Steel: 250
Gold: 500
- type: entity
id: SilverReactorControlRod
parent: BaseReactorControlRod
name: silver control rod
components:
- type: ReactorPart
material: Silver
- type: PhysicalComposition
materialComposition:
Steel: 250
Silver: 500
- type: entity
id: BrassReactorControlRod
parent: BaseReactorControlRod
name: brass control rod
components:
- type: ReactorPart
material: Brass
- type: PhysicalComposition
materialComposition:
Steel: 250
Brass: 500
- type: entity
id: PlasteelReactorControlRod
parent: BaseReactorControlRod
name: plasteel control rod
components:
- type: ReactorPart
material: Plasteel
- type: PhysicalComposition
materialComposition:
Steel: 250
Plasteel: 500
- type: entity
id: GlassReactorControlRod
parent: BaseReactorControlRod
name: glass control rod
components:
- type: ReactorPart
material: Glass
- type: PhysicalComposition
materialComposition:
Steel: 250
Glass: 500
- type: entity
id: PlasmaGlassReactorControlRod
parent: BaseReactorControlRod
name: plasma glass control rod
components:
- type: ReactorPart
material: PlasmaGlass
- type: PhysicalComposition
materialComposition:
Steel: 250
PlasmaGlass: 500
- type: entity
id: DiamondReactorControlRod
parent: BaseReactorControlRod
name: diamond control rod
components:
- type: ReactorPart
material: Diamond
- type: PhysicalComposition
materialComposition:
Steel: 250
Diamond: 500
# Gas Channel Rods
- type: entity
id: BaseReactorGasChannel
parent: BaseReactorPart
categories: [ HideSpawnMenu ]
name: gas channel rod
description: A gas channel for a nuclear reactor.
components:
- type: ReactorPart
rodType: 4
iconStateInserted: "gas"
iconStateCap: "gas_cap"
thermalCrossSection: 15
gasVolume: 100
thermalMass: 21000
- type: PhysicalComposition
materialComposition:
Steel: 250
- type: entity
id: SteelReactorGasChannel
parent: BaseReactorGasChannel
name: steel gas channel rod
components:
- type: ReactorPart
material: Steel
- type: PhysicalComposition
materialComposition:
Steel: 500
- type: entity
id: GoldReactorGasChannel
parent: BaseReactorGasChannel
name: gold gas channel rod
components:
- type: ReactorPart
material: Gold
- type: PhysicalComposition
materialComposition:
Steel: 250
Gold: 250
- type: entity
id: SilverReactorGasChannel
parent: BaseReactorGasChannel
name: silver gas channel rod
components:
- type: ReactorPart
material: Silver
- type: PhysicalComposition
materialComposition:
Steel: 250
Silver: 250
- type: entity
id: BrassReactorGasChannel
parent: BaseReactorGasChannel
name: brass gas channel rod
components:
- type: ReactorPart
material: Brass
- type: PhysicalComposition
materialComposition:
Steel: 250
Brass: 250
- type: entity
id: PlasteelReactorGasChannel
parent: BaseReactorGasChannel
name: plasteel gas channel rod
components:
- type: ReactorPart
material: Plasteel
- type: PhysicalComposition
materialComposition:
Steel: 250
Plasteel: 250
- type: entity
id: GlassReactorGasChannel
parent: BaseReactorGasChannel
name: glass gas channel rod
components:
- type: ReactorPart
material: Glass
- type: PhysicalComposition
materialComposition:
Steel: 250
Glass: 250
- type: entity
id: PlasmaGlassReactorGasChannel
parent: BaseReactorGasChannel
name: plasma glass gas channel rod
components:
- type: ReactorPart
material: PlasmaGlass
- type: PhysicalComposition
materialComposition:
Steel: 250
PlasmaGlass: 250
- type: entity
id: DiamondReactorGasChannel
parent: BaseReactorGasChannel
name: diamond gas channel rod
components:
- type: ReactorPart
material: Diamond
- type: PhysicalComposition
materialComposition:
Steel: 250
Diamond: 250
# Heat Exchanger Rods
- type: entity
id: BaseReactorHeatExchanger
parent: BaseReactorPart
categories: [ HideSpawnMenu ]
name: heat exchanger rod
description: A heat exchanger for a nuclear reactor.
components:
- type: ReactorPart
iconStateInserted: "heat"
iconStateCap: "heat_cap"
neutronCrossSection: 0.1
thermalCrossSection: 25
- type: PhysicalComposition
materialComposition:
Steel: 250
- type: entity
id: SteelReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: steel heat exchanger rod
components:
- type: ReactorPart
material: Steel
- type: PhysicalComposition
materialComposition:
Steel: 625
- type: entity
id: GoldReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: gold heat exchanger rod
components:
- type: ReactorPart
material: Gold
- type: PhysicalComposition
materialComposition:
Steel: 250
Gold: 275
- type: entity
id: SilverReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: silver heat exchanger rod
components:
- type: ReactorPart
material: Silver
- type: PhysicalComposition
materialComposition:
Steel: 250
Silver: 275
- type: entity
id: BrassReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: brass heat exchanger rod
components:
- type: ReactorPart
material: Brass
- type: PhysicalComposition
materialComposition:
Steel: 250
Brass: 275
- type: entity
id: PlasteelReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: plasteel heat exchanger rod
components:
- type: ReactorPart
material: Plasteel
- type: PhysicalComposition
materialComposition:
Steel: 250
Plasteel: 275
- type: entity
id: GlassReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: glass heat exchanger rod
components:
- type: ReactorPart
material: Glass
- type: PhysicalComposition
materialComposition:
Steel: 250
Glass: 275
- type: entity
id: PlasmaGlassReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: plasma glass heat exchanger rod
components:
- type: ReactorPart
material: PlasmaGlass
- type: PhysicalComposition
materialComposition:
Steel: 250
PlasmaGlass: 275
- type: entity
id: DiamondReactorHeatExchanger
parent: BaseReactorHeatExchanger
name: diamond heat exchanger rod
components:
- type: ReactorPart
material: Diamond
- type: PhysicalComposition
materialComposition:
Steel: 250
Diamond: 275
# Parts to chose from for a random prefab
- type: weightedRandom
id: NuclearReactorRandomParts
weights:
# Fuel rods (0.125 total)
CerenkiteReactorFuelRod: 0.0625
UraniumReactorFuelRod: 0.03125
PlutoniumReactorFuelRod: 0.0015625
BananiumReactorFuelRod: 0.00625
PlasmaReactorFuelRod: 0.015625
UraniumGlassReactorFuelRod: 0.0078125
# Control rods (5 total)
BohrumReactorControlRod: 1.25
SteelReactorControlRod: 1.25
GoldReactorControlRod: 0.125
SilverReactorControlRod: 0.25
BrassReactorControlRod: 0.5
PlasteelReactorControlRod: 0.75
GlassReactorControlRod: 0.5
PlasmaGlassReactorControlRod: 0.25
DiamondReactorControlRod: 0.125
# Gas channels (40 total)
SteelReactorGasChannel: 20
GoldReactorGasChannel: 1
SilverReactorGasChannel: 2
BrassReactorGasChannel: 4
PlasteelReactorGasChannel: 6
GlassReactorGasChannel: 4
PlasmaGlassReactorGasChannel: 2
DiamondReactorGasChannel: 1
# Heat exchangers (40 total)
SteelReactorHeatExchanger: 20
GoldReactorHeatExchanger: 1
SilverReactorHeatExchanger: 2
BrassReactorHeatExchanger: 4
PlasteelReactorHeatExchanger: 6
GlassReactorHeatExchanger: 4
PlasmaGlassReactorHeatExchanger: 2
DiamondReactorHeatExchanger: 1
@@ -0,0 +1,239 @@
- type: entity
id: Turbine
parent: BaseStructure
name: gas turbine
description: A large turbine used for generating power using hot gas.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/turbine.rsi
layers:
- state: turbine
visible: true
map: [ "enum.TurbineVisualLayers.TurbineSpeed" ]
- state: turbinebroken
visible: false
map: [ "enum.TurbineVisualLayers.TurbineRuined" ]
- state: turbine_sparks
visible: false
map: [ "enum.TurbineVisualLayers.DamageSpark" ]
- state: turbine_smoke
visible: false
map: [ "enum.TurbineVisualLayers.DamageSmoke" ]
- type: GenericVisualizer
visuals:
enum.TurbineVisuals.TurbineRuined:
enum.TurbineVisualLayers.TurbineRuined:
True: { visible: true }
False: { visible: false }
enum.TurbineVisuals.DamageSpark:
enum.TurbineVisualLayers.DamageSpark:
True: { visible: true }
False: { visible: false }
enum.TurbineVisuals.DamageSmoke:
enum.TurbineVisualLayers.DamageSmoke:
True: { visible: true }
False: { visible: false }
- type: Appearance
- type: Physics
bodyType: Static
- type: InteractionOutline
- type: Rotatable
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-1.5,-2.5,1.5,2.5"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- type: Damageable
damageContainer: StructuralInorganic
damageModifierSet: StructuralMetallicStrong
- type: Anchorable
delay: 10
- type: NodeContainer
examinable: true
nodes:
output:
!type:CableDeviceNode
nodeGroupID: HVPower
- type: PowerMonitoringDevice
group: Generator
loadNode: output
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/turbine.rsi
state: static
- type: PowerSupplier
supplyRampRate: 75000
supplyRampTolerance: 5000
- type: AtmosDevice
- type: AtmosMonitoringConsoleDevice
navMapBlip: Thermoregulator
- type: Electrified
enabled: false
requirePower: false
- type: ContainerContainer
containers:
blade_slot: !type:ContainerSlot {}
stator_slot: !type:ContainerSlot {}
- type: ItemSlots
slots:
blade_slot:
startingItem: SteelGasTurbineBlade
whitelist:
components:
- GasTurbineBlade
stator_slot:
startingItem: SteelGasTurbineStator
whitelist:
components:
- GasTurbineStator
- type: Turbine
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: BasicDevice
- type: WirelessNetworkConnection
range: 200
- type: DeviceLinkSource
range: 1000 # DeltaV - Was 50
ports:
- GasTurbineDataSender
- TurbineSpeedHigh
- TurbineSpeedLow
- type: DeviceLinkSink
ports:
- IncreaseStatorLoad
- DecreaseStatorLoad
- type: ActivatableUI
key: enum.TurbineUiKey.Key
- type: ActivatableUIRequiresAnchor
- type: UserInterface
interfaces:
enum.TurbineUiKey.Key:
type: TurbineBoundUserInterface
- type: AccessReader # Keeps some random tider from blowing up the turbine
access: [["Engineering"]]
- type: Lock
unlockOnClick: false
- type: GuideHelp
guides:
- NuclearReactor
- Power
- type: entity
id: TurbineSmall
parent: Turbine
name: small gas turbine
description: A small turbine used for generating power using hot gas.
components:
- type: Sprite
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/small_turbine.rsi
offset: 0, -0.5
- type: Fixtures
fixtures:
fix1:
shape:
!type:PhysShapeAabb
bounds: "-1.5,-1.5,1.5,0.5"
density: 190
mask:
- MachineMask
layer:
- MachineLayer
- type: PowerMonitoringDevice
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/small_turbine.rsi
- type: Turbine
turbineMass: 400
powerMultiplier: 0.4
inletPos: 1, -1
inletRot: 0
outletPos: -1, -1
outletRot: 0
arrowPrototype: TurbineSmallFlowArrow
# Arrow to indicate flow
- type: entity
id: TurbineFlowArrow
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: Markers/teg_arrow.rsi
color: "#FFFFFFBB"
layers:
- state: arrow
rotation: 90
offset: 2, -1
- state: arrow
rotation: 90
offset: -2, -1
- type: TimedDespawn
lifetime: 2
- type: Tag
tags:
- HideContextMenu
- type: entity
id: TurbineSmallFlowArrow
parent: TurbineFlowArrow
categories: [ HideSpawnMenu ]
components:
- type: Sprite
layers:
- state: arrow
rotation: 180
offset: 1, -2
- state: arrow
rotation: 0
offset: -1, -2
# Pipes the turbine uses to connect to the pipe network
- type: entity
id: TurbineGasPipe
categories: [ HideSpawnMenu ]
components:
- type: Transform
anchored: true
- type: Anchorable
- type: NodeContainer
nodes:
pipe:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 0
pipe1:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 1
pipe2:
!type:PipeNode
nodeGroupID: Pipe
pipeDirection: South
volume: 1000
pipeLayer: 2
- type: GasPipeManifold
inlets:
- pipe1
- pipe2
outlets:
- pipe
- type: PipeRestrictOverlap
- type: Tag
tags:
- HideContextMenu
- type: entity
id: GasTurbineAlarmEntity
categories: [ HideSpawnMenu ]
components:
- type: AmbientSound
volume: -2
range: 15
enabled: false
sound:
path: /Audio/_FarHorizons/Machines/alarm_buzzer.ogg
@@ -0,0 +1,48 @@
- type: entity
id: TurbineBladeShrapnel
name: turbine blade
categories: [ HideSpawnMenu ]
components:
- type: FlyBySound
- type: Sprite
noRot: false
sprite: Objects/Materials/Scrap/generic.rsi
state: metal-1
- type: Physics
bodyType: Dynamic
linearDamping: 0
angularDamping: 0
- type: Fixtures
fixtures:
projectile:
shape:
!type:PhysShapeAabb
bounds: "-0.1,-0.1,0.1,0.1"
hard: false
mask:
- Impassable
- BulletImpassable
fly-by: &flybyfixture
shape: !type:PhysShapeCircle
radius: 1.5
layer:
- Impassable
- MidImpassable
- HighImpassable
- LowImpassable
hard: False
- type: Projectile
impactEffect: BulletImpactEffect
deleteOnCollide: false
damage:
types: # hit hard ye
Piercing: 25
Slash: 75
Structural: 400
soundHit:
path: /Audio/Effects/metal_crunch.ogg
penetrationThreshold: 400
penetrationDamageTypeRequirement:
- Structural
- type: TimedDespawn
lifetime: 0.3
@@ -0,0 +1,11 @@
- type: guideEntry
id: NuclearReactor
name: guide-entry-nuclear-reactor
text: "/ServerInfo/_FarHorizons/Guidebook/Engineering/NuclearReactor.xml"
children:
- NuclearMaterials
- type: guideEntry
id: NuclearMaterials
name: guide-entry-nuclear-materials
text: "/ServerInfo/_FarHorizons/Guidebook/Engineering/NuclearMaterials.xml"
@@ -0,0 +1,8 @@
# Thief Collection
- type: stealTargetGroup
id: ReactorFuelRodSteal
name: steal-target-groups-reactorfuelrod
sprite:
sprite: _FarHorizons/Structures/Power/Generation/FissionGenerator/reactor_parts.rsi
state: default_rod
@@ -0,0 +1,12 @@
- type: entity
parent: BaseThiefStealCollectionObjective
id: ReactorFuelRodStealCollectionObjective
components:
- type: NotJobRequirement
jobs: [StationEngineer, AtmosphericTechnician]
- type: StealCondition
stealGroup: ReactorFuelRodSteal
minCollectionSize: 1
maxCollectionSize: 3
- type: Objective
difficulty: 2 # Large and radioactive
@@ -0,0 +1,44 @@
- type: material
parent: Steel # TODO: Materials
id: Bohrum
name: materials-bohrum
color: "#566558"
price: 0.45 # $45 for 1 unit, arbitrary
properties:
electrical: 5
thermal: 6
hard: 5
density: 6
chemical: 7
- type: material
id: Cerenkite
stackEntity: SheetUranium1 # TODO: Materials
name: materials-cerenkite
icon: { sprite: Objects/Materials/Sheets/other.rsi, state: uranium } # TODO: Materials
color: "#828aa4"
price: 0.65 # $65 for 1 unit, arbitrary
properties:
electrical: 6
thermal: 6
hard: 2
density: 4
chemical: 6
radioactive: 5
- type: material
id: Plutonium
stackEntity: IngotPlutonium1
name: materials-plutonium
unit: materials-unit-bar
icon: { sprite: Objects/Materials/ingots.rsi, state: iron } # Why iron? Because I haven't gotten a spriter to make better
color: "#404888"
price: 1.75 # $175 for 1 unit, arbitrary
properties:
electrical: 7
thermal: 6
hard: 7
density: 8
chemical: 6
radioactive: 3
n_radioactive: 5
@@ -0,0 +1,74 @@
- type: latheRecipePack
id: ReactorCircuitboards
recipes:
- NuclearFabricatorMachineCircuitboard
- NuclearCentrifugeMachineCircuitboard
- NuclearReactorMonitorComputerCircuitboard
- GasTurbineMonitorComputerCircuitboard
- type: latheRecipePack
id: ReactorPartsBasic
recipes:
- CerenkiteReactorFuelRod
- BohrumReactorControlRod
- SteelReactorControlRod
- BrassReactorControlRod
- GlassReactorControlRod
- SteelReactorGasChannel
- BrassReactorGasChannel
- GlassReactorGasChannel
- SteelReactorHeatExchanger
- BrassReactorHeatExchanger
- GlassReactorHeatExchanger
- SteelGasTurbineBlade
- SteelGasTurbineStator
- type: latheRecipePack
id: ReactorFuelRods
recipes:
- UraniumReactorFuelRod
- PlutoniumReactorFuelRod
- BananiumReactorFuelRod
- PlasmaReactorFuelRod
- UraniumGlassReactorFuelRod
- type: latheRecipePack
id: ReactorControlRods
recipes:
- GoldReactorControlRod
- SilverReactorControlRod
- PlasteelReactorControlRod
- PlasmaGlassReactorControlRod
- DiamondReactorControlRod
- type: latheRecipePack
id: ReactorGasChannels
recipes:
- GoldReactorGasChannel
- SilverReactorGasChannel
- PlasteelReactorGasChannel
- PlasmaGlassReactorGasChannel
- DiamondReactorGasChannel
- type: latheRecipePack
id: ReactorHeatExchangers
recipes:
- GoldReactorHeatExchanger
- SilverReactorHeatExchanger
- PlasteelReactorHeatExchanger
- PlasmaGlassReactorHeatExchanger
- DiamondReactorHeatExchanger
- type: latheRecipePack
id: GasTurbineBlades
recipes:
- BrassGasTurbineBlade
- DiamondGasTurbineBlade
- GoldGasTurbineBlade
- PlasteelGasTurbineBlade
- type: latheRecipePack
id: GasTurbineStators
recipes:
- SilverGasTurbineStator
- GoldGasTurbineStator
@@ -0,0 +1,9 @@
- type: latheRecipe
parent: [ BaseCircuitboardRecipe, BaseEngineeringComputerRecipeCategory ]
id: NuclearReactorMonitorComputerCircuitboard
result: NuclearReactorMonitorComputerCircuitboard
- type: latheRecipe
parent: [ BaseCircuitboardRecipe, BaseEngineeringComputerRecipeCategory ]
id: GasTurbineMonitorComputerCircuitboard
result: GasTurbineMonitorComputerCircuitboard
@@ -0,0 +1,9 @@
- type: latheRecipe
parent: [ BaseCircuitboardRecipe, BaseEngineeringMachineRecipeCategory ]
id: NuclearFabricatorMachineCircuitboard
result: NuclearFabricatorMachineCircuitboard
- type: latheRecipe
parent: [ BaseCircuitboardRecipe, BaseEngineeringMachineRecipeCategory ]
id: NuclearCentrifugeMachineCircuitboard
result: NuclearCentrifugeMachineCircuitboard
@@ -0,0 +1,265 @@
# Base prototypes
- type: latheRecipe
abstract: true
id: BaseReactorPartRecipe
categories:
- Engineering
completetime: 10
## Recipes
# Fuel Rods
# 5 Steel + 3 Fuel
- type: latheRecipe
parent: BaseReactorPartRecipe
id: CerenkiteReactorFuelRod
result: CerenkiteReactorFuelRod
materials:
Steel: 500
Plasma: 200
Uranium: 100
- type: latheRecipe
parent: BaseReactorPartRecipe
id: UraniumReactorFuelRod
result: UraniumReactorFuelRod
materials:
Steel: 500
Uranium: 300
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlutoniumReactorFuelRod
result: PlutoniumReactorFuelRod
materials:
Steel: 500
Plutonium: 300
- type: latheRecipe
parent: BaseReactorPartRecipe
id: BananiumReactorFuelRod
result: BananiumReactorFuelRod
materials:
Steel: 500
Bananium: 300
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasmaReactorFuelRod
result: PlasmaReactorFuelRod
materials:
Steel: 500
Plasma: 300
- type: latheRecipe
parent: BaseReactorPartRecipe
id: UraniumGlassReactorFuelRod
result: UraniumGlassReactorFuelRod
materials:
Steel: 500
UraniumGlass: 300
# Control Rods
# 5 Steel + 10 Material
- type: latheRecipe
parent: BaseReactorPartRecipe
id: BohrumReactorControlRod
result: BohrumReactorControlRod
materials:
Steel: 1000
Plasteel: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SteelReactorControlRod
result: SteelReactorControlRod
materials:
Steel: 1500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GoldReactorControlRod
result: GoldReactorControlRod
materials:
Steel: 500
Gold: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SilverReactorControlRod
result: SilverReactorControlRod
materials:
Steel: 500
Silver: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: BrassReactorControlRod
result: BrassReactorControlRod
materials:
Steel: 500
Brass: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasteelReactorControlRod
result: PlasteelReactorControlRod
materials:
Steel: 500
Plasteel: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GlassReactorControlRod
result: GlassReactorControlRod
materials:
Steel: 500
Glass: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasmaGlassReactorControlRod
result: PlasmaGlassReactorControlRod
materials:
Steel: 500
PlasmaGlass: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: DiamondReactorControlRod
result: DiamondReactorControlRod
materials:
Steel: 500
Diamond: 1000
# Gas Channel Rods
# 5 Steel + 5 Material
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SteelReactorGasChannel
result: SteelReactorGasChannel
materials:
Steel: 1000
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GoldReactorGasChannel
result: GoldReactorGasChannel
materials:
Steel: 500
Gold: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SilverReactorGasChannel
result: SilverReactorGasChannel
materials:
Steel: 500
Silver: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: BrassReactorGasChannel
result: BrassReactorGasChannel
materials:
Steel: 500
Brass: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasteelReactorGasChannel
result: PlasteelReactorGasChannel
materials:
Steel: 500
Plasteel: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GlassReactorGasChannel
result: GlassReactorGasChannel
materials:
Steel: 500
Glass: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasmaGlassReactorGasChannel
result: PlasmaGlassReactorGasChannel
materials:
Steel: 500
PlasmaGlass: 500
- type: latheRecipe
parent: BaseReactorPartRecipe
id: DiamondReactorGasChannel
result: DiamondReactorGasChannel
materials:
Steel: 500
Diamond: 500
# Heat Exchanger Rods
# 5 Steel + 7.5 Material
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SteelReactorHeatExchanger
result: SteelReactorHeatExchanger
materials:
Steel: 1250
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GoldReactorHeatExchanger
result: GoldReactorHeatExchanger
materials:
Steel: 500
Gold: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: SilverReactorHeatExchanger
result: SilverReactorHeatExchanger
materials:
Steel: 500
Silver: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: BrassReactorHeatExchanger
result: BrassReactorHeatExchanger
materials:
Steel: 500
Brass: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasteelReactorHeatExchanger
result: PlasteelReactorHeatExchanger
materials:
Steel: 500
Plasteel: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: GlassReactorHeatExchanger
result: GlassReactorHeatExchanger
materials:
Steel: 500
Glass: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: PlasmaGlassReactorHeatExchanger
result: PlasmaGlassReactorHeatExchanger
materials:
Steel: 500
PlasmaGlass: 750
- type: latheRecipe
parent: BaseReactorPartRecipe
id: DiamondReactorHeatExchanger
result: DiamondReactorHeatExchanger
materials:
Steel: 500
Diamond: 750

Some files were not shown because too many files have changed in this diff Show More