Plasma fueled radiation collectors (#19598)

* Fixes issue with solution regenerators

* Update Solution.cs

Removed excess whitespace

* Undid file scoping to make this easier to review

* Initial prototype

* Adjusted coefficients

* Removed unintentional changes from another PR

* Undid unintentional change

* Undid unintentional change

* Added temperature modifier to power production

* Guidebook entry and radiation collector low pressure warning

* Reviewer requested changes
This commit is contained in:
chromiumboy
2023-09-04 08:11:57 -05:00
committed by GitHub
parent cebc3f28f3
commit 05e9d2e33a
6 changed files with 184 additions and 9 deletions

View File

@@ -1,4 +1,7 @@
using Content.Server.Singularity.EntitySystems; 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 namespace Content.Server.Singularity.Components
{ {
@@ -33,5 +36,55 @@ namespace Content.Server.Singularity.Components
/// Timestamp when machine can be deactivated again. /// Timestamp when machine can be deactivated again.
/// </summary> /// </summary>
public TimeSpan CoolDownEnd; public TimeSpan CoolDownEnd;
/// <summary>
/// List of gases that will react to the radiation passing through the collector
/// </summary>
[DataField("radiationReactiveGases")]
[ViewVariables(VVAccess.ReadWrite)]
public List<RadiationReactiveGas>? RadiationReactiveGases;
}
/// <summary>
/// Describes how a gas reacts to the collected radiation
/// </summary>
[DataDefinition]
public sealed partial class RadiationReactiveGas
{
/// <summary>
/// The reactant gas
/// </summary>
[DataField("reactantPrototype", required: true)]
public Gas Reactant = Gas.Plasma;
/// <summary>
/// Multipier for the amount of power produced by the radiation collector when using this gas
/// </summary>
[DataField("powerGenerationEfficiency")]
public float PowerGenerationEfficiency = 1f;
/// <summary>
/// Controls the rate (molar percentage per rad) at which the reactant breaks down when exposed to radiation
/// </summary>
/// /// <remarks>
/// Set to zero if the reactant does not deplete
/// </remarks>
[DataField("reactantBreakdownRate")]
public float ReactantBreakdownRate = 1f;
/// <summary>
/// A byproduct gas that is generated when the reactant breaks down
/// </summary>
/// <remarks>
/// Leave null if the reactant no byproduct gas is to be formed
/// </remarks>
[DataField("byproductPrototype")]
public Gas? Byproduct = null;
/// <summary>
/// The molar ratio of the byproduct gas generated from the reactant gas
/// </summary>
[DataField("molarRatio")]
public float MolarRatio = 1f;
} }
} }

View File

@@ -4,9 +4,12 @@ using Content.Shared.Singularity.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Radiation.Events; using Content.Shared.Radiation.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Timing; 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 namespace Content.Server.Singularity.EntitySystems
{ {
@@ -15,19 +18,36 @@ namespace Content.Server.Singularity.EntitySystems
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<RadiationCollectorComponent, InteractHandEvent>(OnInteractHand); SubscribeLocalEvent<RadiationCollectorComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<RadiationCollectorComponent, OnIrradiatedEvent>(OnRadiation); SubscribeLocalEvent<RadiationCollectorComponent, OnIrradiatedEvent>(OnRadiation);
SubscribeLocalEvent<RadiationCollectorComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<RadiationCollectorComponent, GasAnalyzerScanEvent>(OnAnalyzed);
}
private bool TryGetLoadedGasTank(EntityUid uid, [NotNullWhen(true)] out GasTankComponent? gasTankComponent)
{
gasTankComponent = null;
var container = _containerSystem.EnsureContainer<ContainerSlot>(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) private void OnInteractHand(EntityUid uid, RadiationCollectorComponent component, InteractHandEvent args)
{ {
var curTime = _gameTiming.CurTime; var curTime = _gameTiming.CurTime;
if(curTime < component.CoolDownEnd) if (curTime < component.CoolDownEnd)
return; return;
ToggleCollector(uid, args.User, component); ToggleCollector(uid, args.User, component);
@@ -36,7 +56,37 @@ namespace Content.Server.Singularity.EntitySystems
private void OnRadiation(EntityUid uid, RadiationCollectorComponent component, OnIrradiatedEvent args) 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. // No idea if this is even vaguely accurate to the previous logic.
// The maths is copied from that logic even though it works differently. // 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. // This still won't stop things being potentially hilariously unbalanced though.
if (TryComp<BatteryComponent>(uid, out var batteryComponent)) if (TryComp<BatteryComponent>(uid, out var batteryComponent))
{ {
var charge = args.TotalRads * component.ChargeModifier;
batteryComponent.CurrentCharge += charge; 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<string, GasMixture?> { { Name(uid), gasTankComponent.Air } };
}
public void ToggleCollector(EntityUid uid, EntityUid? user = null, RadiationCollectorComponent? component = null) public void ToggleCollector(EntityUid uid, EntityUid? user = null, RadiationCollectorComponent? component = null)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
SetCollectorEnabled(uid, !component.Enabled, user, component); SetCollectorEnabled(uid, !component.Enabled, user, component);
} }
@@ -61,6 +135,7 @@ namespace Content.Server.Singularity.EntitySystems
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
component.Enabled = enabled; component.Enabled = enabled;
// Show message to the player // 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"; var msg = component.Enabled ? "radiation-collector-component-use-on" : "radiation-collector-component-use-off";
_popupSystem.PopupEntity(Loc.GetString(msg), uid); _popupSystem.PopupEntity(Loc.GetString(msg), uid);
} }
// Update appearance // Update appearance

View File

@@ -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.

View File

@@ -42,7 +42,7 @@
components: components:
- type: StorageFill - type: StorageFill
contents: contents:
- id: RadiationCollector - id: RadiationCollectorFullTank
- type: entity - type: entity
id: CrateEngineeringSingularityContainment id: CrateEngineeringSingularityContainment

View File

@@ -1,7 +1,8 @@
- type: entity - type: entity
id: RadiationCollector id: RadiationCollector
name: radiation collector 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: placement:
mode: SnapgridCenter mode: SnapgridCenter
components: components:
@@ -38,6 +39,12 @@
!type:CableDeviceNode !type:CableDeviceNode
nodeGroupID: HVPower nodeGroupID: HVPower
- type: RadiationCollector - 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) # Note that this doesn't matter too much (see next comment)
# However it does act as a cap on power receivable via the collector. # However it does act as a cap on power receivable via the collector.
- type: Battery - type: Battery
@@ -55,3 +62,38 @@
supplyRampTolerance: 1000000000 supplyRampTolerance: 1000000000
- type: GuideHelp - type: GuideHelp
guides: [ Singularity, Power ] 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

View File

@@ -28,8 +28,11 @@ Emitter lasers and containment field can cause damage, avoid touching them when
## Radition collectors ## Radition collectors
<Box> <Box>
<GuideEntityEmbed Entity="RadiationCollector"/> <GuideEntityEmbed Entity="RadiationCollector"/>
<GuideEntityEmbed Entity="PlasmaTank"/>
</Box> </Box>
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 ## Particle accelerator