diff --git a/Content.Server/Singularity/Components/RadiationCollectorComponent.cs b/Content.Server/Singularity/Components/RadiationCollectorComponent.cs
index fee2c4c729..e958c7dab7 100644
--- a/Content.Server/Singularity/Components/RadiationCollectorComponent.cs
+++ b/Content.Server/Singularity/Components/RadiationCollectorComponent.cs
@@ -1,4 +1,7 @@
using Content.Server.Singularity.EntitySystems;
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Singularity.Components
{
@@ -33,5 +36,55 @@ namespace Content.Server.Singularity.Components
/// Timestamp when machine can be deactivated again.
///
public TimeSpan CoolDownEnd;
+
+ ///
+ /// List of gases that will react to the radiation passing through the collector
+ ///
+ [DataField("radiationReactiveGases")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public List? RadiationReactiveGases;
+ }
+
+ ///
+ /// Describes how a gas reacts to the collected radiation
+ ///
+ [DataDefinition]
+ public sealed partial class RadiationReactiveGas
+ {
+ ///
+ /// The reactant gas
+ ///
+ [DataField("reactantPrototype", required: true)]
+ public Gas Reactant = Gas.Plasma;
+
+ ///
+ /// Multipier for the amount of power produced by the radiation collector when using this gas
+ ///
+ [DataField("powerGenerationEfficiency")]
+ public float PowerGenerationEfficiency = 1f;
+
+ ///
+ /// Controls the rate (molar percentage per rad) at which the reactant breaks down when exposed to radiation
+ ///
+ /// ///
+ /// Set to zero if the reactant does not deplete
+ ///
+ [DataField("reactantBreakdownRate")]
+ public float ReactantBreakdownRate = 1f;
+
+ ///
+ /// A byproduct gas that is generated when the reactant breaks down
+ ///
+ ///
+ /// Leave null if the reactant no byproduct gas is to be formed
+ ///
+ [DataField("byproductPrototype")]
+ public Gas? Byproduct = null;
+
+ ///
+ /// The molar ratio of the byproduct gas generated from the reactant gas
+ ///
+ [DataField("molarRatio")]
+ public float MolarRatio = 1f;
}
}
diff --git a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
index 142686fcd0..8fa05386e2 100644
--- a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
+++ b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs
@@ -4,9 +4,12 @@ using Content.Shared.Singularity.Components;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Shared.Radiation.Events;
-using Robust.Server.GameObjects;
using Robust.Shared.Timing;
-using Robust.Shared.Player;
+using Robust.Shared.Containers;
+using Content.Server.Atmos.Components;
+using Content.Shared.Examine;
+using Content.Server.Atmos;
+using System.Diagnostics.CodeAnalysis;
namespace Content.Server.Singularity.EntitySystems
{
@@ -15,19 +18,36 @@ namespace Content.Server.Singularity.EntitySystems
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnInteractHand);
SubscribeLocalEvent(OnRadiation);
+ SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnAnalyzed);
+ }
+
+ private bool TryGetLoadedGasTank(EntityUid uid, [NotNullWhen(true)] out GasTankComponent? gasTankComponent)
+ {
+ gasTankComponent = null;
+ var container = _containerSystem.EnsureContainer(uid, "GasTank");
+
+ if (container.ContainedEntity == null)
+ return false;
+
+ if (!EntityManager.TryGetComponent(container.ContainedEntity, out gasTankComponent))
+ return false;
+
+ return true;
}
private void OnInteractHand(EntityUid uid, RadiationCollectorComponent component, InteractHandEvent args)
{
var curTime = _gameTiming.CurTime;
- if(curTime < component.CoolDownEnd)
+ if (curTime < component.CoolDownEnd)
return;
ToggleCollector(uid, args.User, component);
@@ -36,7 +56,37 @@ namespace Content.Server.Singularity.EntitySystems
private void OnRadiation(EntityUid uid, RadiationCollectorComponent component, OnIrradiatedEvent args)
{
- if (!component.Enabled) return;
+ if (!component.Enabled || component.RadiationReactiveGases == null)
+ return;
+
+ if (!TryGetLoadedGasTank(uid, out var gasTankComponent))
+ return;
+
+ var charge = 0f;
+
+ foreach (var gas in component.RadiationReactiveGases)
+ {
+ float reactantMol = gasTankComponent.Air.GetMoles(gas.Reactant);
+ float delta = args.TotalRads * reactantMol * gas.ReactantBreakdownRate;
+
+ // We need to offset the huge power gains possible when using very cold gases
+ // (they allow you to have a much higher molar concentrations of gas in the tank).
+ // Hence power output is modified using the Michaelis-Menten equation,
+ // it will heavily penalise the power output of low temperature reactions:
+ // 300K = 100% power output, 73K = 49% power output, 1K = 1% power output
+ float temperatureMod = 1.5f * gasTankComponent.Air.Temperature / (150f + gasTankComponent.Air.Temperature);
+ charge += args.TotalRads * reactantMol * component.ChargeModifier * gas.PowerGenerationEfficiency * temperatureMod;
+
+ if (delta > 0)
+ {
+ gasTankComponent.Air.AdjustMoles(gas.Reactant, -Math.Min(delta, reactantMol));
+ }
+
+ if (gas.Byproduct != null)
+ {
+ gasTankComponent.Air.AdjustMoles((int) gas.Byproduct, delta * gas.MolarRatio);
+ }
+ }
// No idea if this is even vaguely accurate to the previous logic.
// The maths is copied from that logic even though it works differently.
@@ -45,15 +95,39 @@ namespace Content.Server.Singularity.EntitySystems
// This still won't stop things being potentially hilariously unbalanced though.
if (TryComp(uid, out var batteryComponent))
{
- var charge = args.TotalRads * component.ChargeModifier;
batteryComponent.CurrentCharge += charge;
}
}
+ private void OnExamined(EntityUid uid, RadiationCollectorComponent component, ExaminedEvent args)
+ {
+ if (!TryGetLoadedGasTank(uid, out var gasTankComponent))
+ {
+ args.PushMarkup(Loc.GetString("power-radiation-collector-gas-tank-missing"));
+ return;
+ }
+
+ args.PushMarkup(Loc.GetString("power-radiation-collector-gas-tank-present"));
+
+ if (gasTankComponent.IsLowPressure)
+ {
+ args.PushMarkup(Loc.GetString("power-radiation-collector-gas-tank-low-pressure"));
+ }
+ }
+
+ private void OnAnalyzed(EntityUid uid, RadiationCollectorComponent component, GasAnalyzerScanEvent args)
+ {
+ if (!TryGetLoadedGasTank(uid, out var gasTankComponent))
+ return;
+
+ args.GasMixtures = new Dictionary { { Name(uid), gasTankComponent.Air } };
+ }
+
public void ToggleCollector(EntityUid uid, EntityUid? user = null, RadiationCollectorComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
+
SetCollectorEnabled(uid, !component.Enabled, user, component);
}
@@ -61,6 +135,7 @@ namespace Content.Server.Singularity.EntitySystems
{
if (!Resolve(uid, ref component))
return;
+
component.Enabled = enabled;
// Show message to the player
@@ -68,7 +143,6 @@ namespace Content.Server.Singularity.EntitySystems
{
var msg = component.Enabled ? "radiation-collector-component-use-on" : "radiation-collector-component-use-off";
_popupSystem.PopupEntity(Loc.GetString(msg), uid);
-
}
// Update appearance
diff --git a/Resources/Locale/en-US/power/components/radiation-collector.ftl b/Resources/Locale/en-US/power/components/radiation-collector.ftl
new file mode 100644
index 0000000000..d68296fbea
--- /dev/null
+++ b/Resources/Locale/en-US/power/components/radiation-collector.ftl
@@ -0,0 +1,3 @@
+power-radiation-collector-gas-tank-missing = [color=red]No gas tank attached.[/color]
+power-radiation-collector-gas-tank-present = A gas tank is [color=darkgreen]connected[/color].
+power-radiation-collector-gas-tank-low-pressure = The gas tank [color=orange]low pressure[/color] light is on.
\ No newline at end of file
diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
index 11e0224207..99f937b1e3 100644
--- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
+++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml
@@ -42,7 +42,7 @@
components:
- type: StorageFill
contents:
- - id: RadiationCollector
+ - id: RadiationCollectorFullTank
- type: entity
id: CrateEngineeringSingularityContainment
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml
index 9fe95c7c27..ecdc3b3fbc 100644
--- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/collector.yml
@@ -1,7 +1,8 @@
- type: entity
id: RadiationCollector
name: radiation collector
- description: A machine that collects radiation and turns it into power.
+ suffix: Empty tank
+ description: A machine that collects radiation and turns it into power. Requires plasma gas to function.
placement:
mode: SnapgridCenter
components:
@@ -38,6 +39,12 @@
!type:CableDeviceNode
nodeGroupID: HVPower
- type: RadiationCollector
+ chargeModifier: 7500
+ radiationReactiveGases:
+ - reactantPrototype: Plasma
+ powerGenerationEfficiency: 1
+ reactantBreakdownRate: 0.0002
+ byproductPrototype: Tritium
# Note that this doesn't matter too much (see next comment)
# However it does act as a cap on power receivable via the collector.
- type: Battery
@@ -55,3 +62,38 @@
supplyRampTolerance: 1000000000
- type: GuideHelp
guides: [ Singularity, Power ]
+ - type: ContainerContainer
+ containers:
+ GasTank: !type:ContainerSlot {}
+ - type: ItemSlots
+ slots:
+ GasTank:
+ startingItem: PlasmaTank
+ whitelist:
+ components:
+ - GasTank
+
+- type: entity
+ id: RadiationCollectorNoTank
+ suffix: No tank
+ parent: RadiationCollector
+ components:
+ - type: ItemSlots
+ slots:
+ GasTank:
+ whitelist:
+ components:
+ - GasTank
+
+- type: entity
+ id: RadiationCollectorFullTank
+ suffix: Filled tank
+ parent: RadiationCollector
+ components:
+ - type: ItemSlots
+ slots:
+ GasTank:
+ startingItem: PlasmaTankFilled
+ whitelist:
+ components:
+ - GasTank
\ No newline at end of file
diff --git a/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml b/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml
index 76115fb437..5ebcd0d7d6 100644
--- a/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml
+++ b/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml
@@ -28,8 +28,11 @@ Emitter lasers and containment field can cause damage, avoid touching them when
## Radition collectors
+
-They connect to HV cables and generate power from nearby radiation sources when turned on.
+They connect to HV cables and generate power from nearby radiation sources when turned on.
+Radiation collectors require a tank full of gaseous plasma in order to operate.
+Continous radiation exposure will gradually convert the stored plasma into tritium, so replace depleted plasma tanks with fresh ones to maintain a high power output.
## Particle accelerator