diff --git a/Content.Server/Singularity/Components/RadiationCollectorComponent.cs b/Content.Server/Singularity/Components/RadiationCollectorComponent.cs index e958c7dab7..649514ed42 100644 --- a/Content.Server/Singularity/Components/RadiationCollectorComponent.cs +++ b/Content.Server/Singularity/Components/RadiationCollectorComponent.cs @@ -1,90 +1,87 @@ 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; + +/// +/// Generates electricity from radiation. +/// +[RegisterComponent] +[Access(typeof(RadiationCollectorSystem))] +public sealed partial class RadiationCollectorComponent : Component { /// - /// Generates electricity from radiation. + /// How much joules will collector generate for each rad. /// - [RegisterComponent] - [Access(typeof(RadiationCollectorSystem))] - public sealed partial class RadiationCollectorComponent : Component - { - /// - /// How much joules will collector generate for each rad. - /// - [DataField("chargeModifier")] - [ViewVariables(VVAccess.ReadWrite)] - public float ChargeModifier = 30000f; - - /// - /// Cooldown time between users interaction. - /// - [DataField("cooldown")] - [ViewVariables(VVAccess.ReadWrite)] - public TimeSpan Cooldown = TimeSpan.FromSeconds(0.81f); - - /// - /// Was machine activated by user? - /// - [ViewVariables(VVAccess.ReadOnly)] - public bool Enabled; - - /// - /// 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; - } + [DataField("chargeModifier")] + [ViewVariables(VVAccess.ReadWrite)] + public float ChargeModifier = 30000f; /// - /// Describes how a gas reacts to the collected radiation + /// Cooldown time between users interaction. /// - [DataDefinition] - public sealed partial class RadiationReactiveGas - { - /// - /// The reactant gas - /// - [DataField("reactantPrototype", required: true)] - public Gas Reactant = Gas.Plasma; + [DataField("cooldown")] + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Cooldown = TimeSpan.FromSeconds(0.81f); - /// - /// Multipier for the amount of power produced by the radiation collector when using this gas - /// - [DataField("powerGenerationEfficiency")] - public float PowerGenerationEfficiency = 1f; + /// + /// Was machine activated by user? + /// + [ViewVariables(VVAccess.ReadOnly)] + public bool Enabled; - /// - /// 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; + /// + /// Timestamp when machine can be deactivated again. + /// + public TimeSpan CoolDownEnd; - /// - /// 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; - } + /// + /// 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 8fa05386e2..19d9b98f4a 100644 --- a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs @@ -11,151 +11,150 @@ using Content.Shared.Examine; using Content.Server.Atmos; using System.Diagnostics.CodeAnalysis; -namespace Content.Server.Singularity.EntitySystems +namespace Content.Server.Singularity.EntitySystems; + +public sealed class RadiationCollectorSystem : EntitySystem { - public sealed class RadiationCollectorSystem : EntitySystem + [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() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnRadiation); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnAnalyzed); + } - public override void Initialize() + 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) + return; + + ToggleCollector(uid, args.User, component); + component.CoolDownEnd = curTime + component.Cooldown; + } + + private void OnRadiation(EntityUid uid, RadiationCollectorComponent component, OnIrradiatedEvent args) + { + if (!component.Enabled || component.RadiationReactiveGases == null) + return; + + if (!TryGetLoadedGasTank(uid, out var gasTankComponent)) + return; + + var charge = 0f; + + foreach (var gas in component.RadiationReactiveGases) { - base.Initialize(); - SubscribeLocalEvent(OnInteractHand); - SubscribeLocalEvent(OnRadiation); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnAnalyzed); - } + float reactantMol = gasTankComponent.Air.GetMoles(gas.Reactant); + float delta = args.TotalRads * reactantMol * gas.ReactantBreakdownRate; - private bool TryGetLoadedGasTank(EntityUid uid, [NotNullWhen(true)] out GasTankComponent? gasTankComponent) - { - gasTankComponent = null; - var container = _containerSystem.EnsureContainer(uid, "GasTank"); + // 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 (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) - return; - - ToggleCollector(uid, args.User, component); - component.CoolDownEnd = curTime + component.Cooldown; - } - - private void OnRadiation(EntityUid uid, RadiationCollectorComponent component, OnIrradiatedEvent args) - { - if (!component.Enabled || component.RadiationReactiveGases == null) - return; - - if (!TryGetLoadedGasTank(uid, out var gasTankComponent)) - return; - - var charge = 0f; - - foreach (var gas in component.RadiationReactiveGases) + if (delta > 0) { - 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); - } + gasTankComponent.Air.AdjustMoles(gas.Reactant, -Math.Min(delta, reactantMol)); } - // No idea if this is even vaguely accurate to the previous logic. - // The maths is copied from that logic even though it works differently. - // But the previous logic would also make the radiation collectors never ever stop providing energy. - // And since frameTime was used there, I'm assuming that this is what the intent was. - // This still won't stop things being potentially hilariously unbalanced though. - if (TryComp(uid, out var batteryComponent)) + if (gas.Byproduct != null) { - batteryComponent.CurrentCharge += charge; + gasTankComponent.Air.AdjustMoles((int) gas.Byproduct, delta * gas.MolarRatio); } } - private void OnExamined(EntityUid uid, RadiationCollectorComponent component, ExaminedEvent args) + // No idea if this is even vaguely accurate to the previous logic. + // The maths is copied from that logic even though it works differently. + // But the previous logic would also make the radiation collectors never ever stop providing energy. + // And since frameTime was used there, I'm assuming that this is what the intent was. + // This still won't stop things being potentially hilariously unbalanced though. + if (TryComp(uid, out var batteryComponent)) { - 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); - } - - public void SetCollectorEnabled(EntityUid uid, bool enabled, EntityUid? user = null, RadiationCollectorComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Enabled = enabled; - - // Show message to the player - if (user != null) - { - var msg = component.Enabled ? "radiation-collector-component-use-on" : "radiation-collector-component-use-off"; - _popupSystem.PopupEntity(Loc.GetString(msg), uid); - } - - // Update appearance - UpdateAppearance(uid, component); - } - - private void UpdateAppearance(EntityUid uid, RadiationCollectorComponent? component, AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref component, ref appearance)) - return; - - var state = component.Enabled ? RadiationCollectorVisualState.Active : RadiationCollectorVisualState.Deactive; - _appearance.SetData(uid, RadiationCollectorVisuals.VisualState, state, appearance); + 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); + } + + public void SetCollectorEnabled(EntityUid uid, bool enabled, EntityUid? user = null, RadiationCollectorComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.Enabled = enabled; + + // Show message to the player + if (user != null) + { + var msg = component.Enabled ? "radiation-collector-component-use-on" : "radiation-collector-component-use-off"; + _popupSystem.PopupEntity(Loc.GetString(msg), uid); + } + + // Update appearance + UpdateAppearance(uid, component); + } + + private void UpdateAppearance(EntityUid uid, RadiationCollectorComponent? component, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref component, ref appearance)) + return; + + var state = component.Enabled ? RadiationCollectorVisualState.Active : RadiationCollectorVisualState.Deactive; + _appearance.SetData(uid, RadiationCollectorVisuals.VisualState, state, appearance); + } }