diff --git a/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs b/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs new file mode 100644 index 0000000000..aec138eed8 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasMinerSystem.cs @@ -0,0 +1,10 @@ +using Content.Shared.Atmos.EntitySystems; +using JetBrains.Annotations; + +namespace Content.Client.Atmos.EntitySystems; + +[UsedImplicitly] +public sealed class GasMinerSystem : SharedGasMinerSystem +{ + +} diff --git a/Content.Server/Atmos/EntitySystems/GasMinerSystem.cs b/Content.Server/Atmos/EntitySystems/GasMinerSystem.cs new file mode 100644 index 0000000000..f7a3b9eed8 --- /dev/null +++ b/Content.Server/Atmos/EntitySystems/GasMinerSystem.cs @@ -0,0 +1,90 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Atmos.Piping.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; +using JetBrains.Annotations; +using Robust.Server.GameObjects; + +namespace Content.Server.Atmos.EntitySystems; + +[UsedImplicitly] +public sealed class GasMinerSystem : SharedGasMinerSystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMinerUpdated); + } + + private void OnMinerUpdated(Entity ent, ref AtmosDeviceUpdateEvent args) + { + var miner = ent.Comp; + var oldState = miner.MinerState; + float toSpawn; + + if (!GetValidEnvironment(ent, out var environment) || !Transform(ent).Anchored) + { + miner.MinerState = GasMinerState.Disabled; + } + // SpawnAmount is declared in mol/s so to get the amount of gas we hope to mine, we have to multiply this by + // how long we have been waiting to spawn it and further cap the number according to the miner's state. + else if ((toSpawn = CapSpawnAmount(ent, miner.SpawnAmount * args.dt, environment)) == 0) + { + miner.MinerState = GasMinerState.Idle; + } + else + { + miner.MinerState = GasMinerState.Working; + + // Time to mine some gas. + var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature }; + merger.SetMoles(miner.SpawnGas, toSpawn); + _atmosphereSystem.Merge(environment, merger); + } + + if (miner.MinerState != oldState) + { + Dirty(ent); + } + } + + private bool GetValidEnvironment(Entity ent, [NotNullWhen(true)] out GasMixture? environment) + { + var (uid, miner) = ent; + var transform = Transform(uid); + var position = _transformSystem.GetGridOrMapTilePosition(uid, transform); + + // Treat space as an invalid environment + if (_atmosphereSystem.IsTileSpace(transform.GridUid, transform.MapUid, position)) + { + environment = null; + return false; + } + + environment = _atmosphereSystem.GetContainingMixture((uid, transform), true, true); + return environment != null; + } + + private float CapSpawnAmount(Entity ent, float toSpawnTarget, GasMixture environment) + { + var (uid, miner) = ent; + + // How many moles could we theoretically spawn. Cap by pressure and amount. + var allowableMoles = Math.Min( + (miner.MaxExternalPressure - environment.Pressure) * environment.Volume / (miner.SpawnTemperature * Atmospherics.R), + miner.MaxExternalAmount - environment.TotalMoles); + + var toSpawnReal = Math.Clamp(allowableMoles, 0f, toSpawnTarget); + + if (toSpawnReal < Atmospherics.GasMinMoles) { + return 0f; + } + + return toSpawnReal; + } +} diff --git a/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs b/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs deleted file mode 100644 index 1775ec5dad..0000000000 --- a/Content.Server/Atmos/Piping/Other/Components/GasMinerComponent.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Shared.Atmos; - -namespace Content.Server.Atmos.Piping.Other.Components -{ - [RegisterComponent] - public sealed partial class GasMinerComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - public bool Enabled { get; set; } = true; - - [ViewVariables(VVAccess.ReadOnly)] - public bool Idle { get; set; } = false; - - /// - /// If the number of moles in the external environment exceeds this number, no gas will be mined. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxExternalAmount")] - public float MaxExternalAmount { get; set; } = float.PositiveInfinity; - - /// - /// If the pressure (in kPA) of the external environment exceeds this number, no gas will be mined. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxExternalPressure")] - public float MaxExternalPressure { get; set; } = Atmospherics.GasMinerDefaultMaxExternalPressure; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("spawnGas")] - public Gas? SpawnGas { get; set; } = null; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("spawnTemperature")] - public float SpawnTemperature { get; set; } = Atmospherics.T20C; - - /// - /// Number of moles created per second when the miner is working. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("spawnAmount")] - public float SpawnAmount { get; set; } = Atmospherics.MolesCellStandard * 20f; - } -} diff --git a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs b/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs deleted file mode 100644 index dd46fb6b4c..0000000000 --- a/Content.Server/Atmos/Piping/Other/EntitySystems/GasMinerSystem.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Atmos.Piping.Components; -using Content.Server.Atmos.Piping.Other.Components; -using Content.Shared.Atmos; -using JetBrains.Annotations; -using Robust.Server.GameObjects; - -namespace Content.Server.Atmos.Piping.Other.EntitySystems -{ - [UsedImplicitly] - public sealed class GasMinerSystem : EntitySystem - { - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMinerUpdated); - } - - private void OnMinerUpdated(Entity ent, ref AtmosDeviceUpdateEvent args) - { - var miner = ent.Comp; - - if (!GetValidEnvironment(ent, out var environment)) - { - miner.Idle = true; - return; - } - - // SpawnAmount is declared in mol/s so to get the amount of gas we hope to mine, we have to multiply this by - // how long we have been waiting to spawn it and further cap the number according to the miner's state. - var toSpawn = CapSpawnAmount(ent, miner.SpawnAmount * args.dt, environment); - miner.Idle = toSpawn == 0; - if (miner.Idle || !miner.Enabled || !miner.SpawnGas.HasValue) - return; - - // Time to mine some gas. - - var merger = new GasMixture(1) { Temperature = miner.SpawnTemperature }; - merger.SetMoles(miner.SpawnGas.Value, toSpawn); - - _atmosphereSystem.Merge(environment, merger); - } - - private bool GetValidEnvironment(Entity ent, [NotNullWhen(true)] out GasMixture? environment) - { - var (uid, miner) = ent; - var transform = Transform(uid); - var position = _transformSystem.GetGridOrMapTilePosition(uid, transform); - - // Treat space as an invalid environment - if (_atmosphereSystem.IsTileSpace(transform.GridUid, transform.MapUid, position)) - { - environment = null; - return false; - } - - environment = _atmosphereSystem.GetContainingMixture((uid, transform), true, true); - return environment != null; - } - - private float CapSpawnAmount(Entity ent, float toSpawnTarget, GasMixture environment) - { - var (uid, miner) = ent; - - // How many moles could we theoretically spawn. Cap by pressure and amount. - var allowableMoles = Math.Min( - (miner.MaxExternalPressure - environment.Pressure) * environment.Volume / (miner.SpawnTemperature * Atmospherics.R), - miner.MaxExternalAmount - environment.TotalMoles); - - var toSpawnReal = Math.Clamp(allowableMoles, 0f, toSpawnTarget); - - if (toSpawnReal < Atmospherics.GasMinMoles) { - return 0f; - } - - return toSpawnReal; - } - } -} diff --git a/Content.Shared/Atmos/Components/GasMinerComponent.cs b/Content.Shared/Atmos/Components/GasMinerComponent.cs new file mode 100644 index 0000000000..43f3032770 --- /dev/null +++ b/Content.Shared/Atmos/Components/GasMinerComponent.cs @@ -0,0 +1,60 @@ +using Robust.Shared.Serialization; +using Robust.Shared.GameStates; + +namespace Content.Shared.Atmos.Components; + +[NetworkedComponent] +[AutoGenerateComponentState] +[RegisterComponent] +public sealed partial class GasMinerComponent : Component +{ + /// + /// Operational state of the miner. + /// + [AutoNetworkedField] + [ViewVariables(VVAccess.ReadOnly)] + public GasMinerState MinerState = GasMinerState.Disabled; + + /// + /// If the number of moles in the external environment exceeds this number, no gas will be mined. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float MaxExternalAmount = float.PositiveInfinity; + + /// + /// If the pressure (in kPA) of the external environment exceeds this number, no gas will be mined. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float MaxExternalPressure = Atmospherics.GasMinerDefaultMaxExternalPressure; + + /// + /// Gas to spawn. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] + public Gas SpawnGas; + + /// + /// Temperature in Kelvin. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float SpawnTemperature = Atmospherics.T20C; + + /// + /// Number of moles created per second when the miner is working. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float SpawnAmount = Atmospherics.MolesCellStandard * 20f; +} + +[Serializable, NetSerializable] +public enum GasMinerState : byte +{ + Disabled, + Idle, + Working, +} diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasMinerSystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasMinerSystem.cs new file mode 100644 index 0000000000..957ff6fde4 --- /dev/null +++ b/Content.Shared/Atmos/EntitySystems/SharedGasMinerSystem.cs @@ -0,0 +1,55 @@ +using Content.Shared.Atmos.Components; +using Content.Shared.Examine; +using Content.Shared.Temperature; + +namespace Content.Shared.Atmos.EntitySystems; + +public abstract class SharedGasMinerSystem : EntitySystem +{ + [Dependency] private readonly SharedAtmosphereSystem _sharedAtmosphereSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnExamine); + } + + private void OnExamine(Entity ent, ref ExaminedEvent args) + { + var component = ent.Comp; + + using (args.PushGroup(nameof(GasMinerComponent))) + { + args.PushMarkup(Loc.GetString("gas-miner-mines-text", + ("gas", Loc.GetString(_sharedAtmosphereSystem.GetGas(component.SpawnGas).Name)))); + + args.PushText(Loc.GetString("gas-miner-amount-text", + ("moles", $"{component.SpawnAmount:0.#}"))); + + args.PushText(Loc.GetString("gas-miner-temperature-text", + ("tempK", $"{component.SpawnTemperature:0.#}"), + ("tempC", $"{TemperatureHelpers.KelvinToCelsius(component.SpawnTemperature):0.#}"))); + + if (component.MaxExternalAmount < float.PositiveInfinity) + { + args.PushText(Loc.GetString("gas-miner-moles-cutoff-text", + ("moles", $"{component.MaxExternalAmount:0.#}"))); + } + + if (component.MaxExternalPressure < float.PositiveInfinity) + { + args.PushText(Loc.GetString("gas-miner-pressure-cutoff-text", + ("pressure", $"{component.MaxExternalPressure:0.#}"))); + } + + args.AddMarkup(component.MinerState switch + { + GasMinerState.Disabled => Loc.GetString("gas-miner-state-disabled-text"), + GasMinerState.Idle => Loc.GetString("gas-miner-state-idle-text"), + GasMinerState.Working => Loc.GetString("gas-miner-state-working-text"), + // C# pattern matching is not exhaustive for enums + _ => throw new IndexOutOfRangeException(nameof(component.MinerState)), + }); + } + } +} diff --git a/Resources/Locale/en-US/atmos/gas-miner-component.ftl b/Resources/Locale/en-US/atmos/gas-miner-component.ftl new file mode 100644 index 0000000000..87016e4522 --- /dev/null +++ b/Resources/Locale/en-US/atmos/gas-miner-component.ftl @@ -0,0 +1,11 @@ +gas-miner-mines-text = It mines [color=lightgray]{$gas}[/color] when active. + +gas-miner-amount-text = It mines {$moles} moles of gas a second when active. +gas-miner-temperature-text = Mined gas temp: {$tempK}K ({$tempC}°C). + +gas-miner-moles-cutoff-text = Surrounding moles cutoff: {$moles} moles. +gas-miner-pressure-cutoff-text = Surrounding pressure cutoff: {$pressure} kPA. + +gas-miner-state-working-text = The miner is [color=green]active[/color] and mining gas. +gas-miner-state-idle-text = The miner is [color=yellow]idle[/color] and not mining gas. +gas-miner-state-disabled-text = The miner is [color=red]disabled[/color] and not mining gas. \ No newline at end of file