diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs index f0e4b13356c..5432357368f 100644 --- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -31,6 +31,30 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state", }; + private Dictionary _gasShorthands = new Dictionary() + { + [Gas.Ammonia] = "NH₃", + [Gas.CarbonDioxide] = "CO₂", + [Gas.Frezon] = "F", + [Gas.Nitrogen] = "N₂", + [Gas.NitrousOxide] = "N₂O", + [Gas.Oxygen] = "O₂", + [Gas.Plasma] = "P", + [Gas.Tritium] = "T", + [Gas.WaterVapor] = "H₂O", + [Gas.BZ] = "BZ", + [Gas.Healium] = "F₃BZ", + [Gas.Nitrium] = "N", + [Gas.Pluoxium] = "Pl₂", + [Gas.Hydrogen] = "H₂", + [Gas.HyperNoblium] = "HN₂", + [Gas.ProtoNitrate] = "PN₂", + [Gas.Zauker] = "Z₂", + [Gas.Halon] = "Ha₂", + [Gas.Helium] = "He₂", + [Gas.AntiNoblium] = "AN₂", + }; + public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates) { RobustXamlLoader.Load(this); @@ -149,11 +173,12 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs) { FixedPoint2 gasPercent = percent * 100f; - var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); + + var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "X"); var gasLabel = new Label() { - Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)), + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)), FontOverride = normalFont, FontColorOverride = GetAlarmStateColor(alert), HorizontalAlignment = HAlignment.Center, diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs index 0ce0c9c880a..5652c6a41ef 100644 --- a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs @@ -1,6 +1,7 @@ using Content.Client.Stylesheets; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.FixedPoint; using Content.Shared.Temperature; using Robust.Client.AutoGenerated; @@ -19,12 +20,14 @@ public sealed partial class AtmosMonitoringEntryContainer : BoxContainer private readonly IEntityManager _entManager; private readonly IResourceCache _cache; + private readonly SharedAtmosphereSystem _atmosphereSystem; // Adventure edit public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data) { RobustXamlLoader.Load(this); _entManager = IoCManager.Resolve(); _cache = IoCManager.Resolve(); + _atmosphereSystem = _entManager.System(); // Adventure edit Data = data; @@ -132,7 +135,10 @@ public void UpdateEntry(AtmosMonitoringConsoleEntry updatedData, bool isFocus) var gasPercent = (FixedPoint2)0f; gasPercent = percent * 100f; - var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); + var proto = ((int)gas >= 0 && (int)gas < Atmospherics.TotalNumberOfGases) ? _atmosphereSystem.GetGas((int)gas) : null; + var gasAbbreviation = proto?.Abbreviation is { } abbrev + ? Loc.GetString(abbrev.Id ?? "gas-unknown-abbreviation") + : Loc.GetString("gas-unknown-abbreviation"); var gasLabel = new Label() { diff --git a/Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs index b63d274bdca..32e82922418 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosDebugOverlaySystem.cs @@ -10,7 +10,9 @@ namespace Content.Client.Atmos.EntitySystems [UsedImplicitly] internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem { - public readonly Dictionary TileData = new(); + [Dependency] private readonly IOverlayManager _overlayManager = default!; + + public readonly Dictionary TileData = []; // Configuration set by debug commands and used by AtmosDebugOverlay { /// Value source for display @@ -25,6 +27,8 @@ internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem public bool CfgCBM = false; // } + private AtmosDebugOverlay? _overlay; + public override void Initialize() { base.Initialize(); @@ -34,10 +38,6 @@ public override void Initialize() SubscribeNetworkEvent(HandleAtmosDebugOverlayDisableMessage); SubscribeLocalEvent(OnGridRemoved); - - var overlayManager = IoCManager.Resolve(); - if(!overlayManager.HasOverlay()) - overlayManager.AddOverlay(new AtmosDebugOverlay(this)); } private void OnGridRemoved(GridRemovalEvent ev) @@ -51,19 +51,25 @@ private void OnGridRemoved(GridRemovalEvent ev) private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message) { TileData[GetEntity(message.GridId)] = message; + + if (_overlay is not null) + return; + + _overlay = new AtmosDebugOverlay(this); + _overlayManager.AddOverlay(_overlay); } private void HandleAtmosDebugOverlayDisableMessage(AtmosDebugOverlayDisableMessage ev) { TileData.Clear(); + RemoveOverlay(); } public override void Shutdown() { base.Shutdown(); - var overlayManager = IoCManager.Resolve(); - if (overlayManager.HasOverlay()) - overlayManager.RemoveOverlay(); + + RemoveOverlay(); } public void Reset(RoundRestartCleanupEvent ev) @@ -75,6 +81,15 @@ public bool HasData(EntityUid gridId) { return TileData.ContainsKey(gridId); } + + private void RemoveOverlay() + { + if (_overlay is null) + return; + + _overlayManager.RemoveOverlay(_overlay); + _overlay = null; + } } internal enum AtmosDebugOverlayMode : byte diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.CVars.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.CVars.cs new file mode 100644 index 00000000000..bf6fdaf0716 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.CVars.cs @@ -0,0 +1,17 @@ +using Content.Shared.CCVar; +using Robust.Shared.Configuration; + +namespace Content.Client.Atmos.EntitySystems; + +public sealed partial class AtmosphereSystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private void InitializeCVars() + { + _cfg.OnValueChanged( + CCVars.AtmosHeatScale, + v => HeatScale = MathF.Max(0.000001f, v), + invokeImmediately: true); + } +} diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs new file mode 100644 index 00000000000..0da101d32c6 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -0,0 +1,58 @@ +using System.Runtime.CompilerServices; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; + +namespace Content.Client.Atmos.EntitySystems; + +public sealed partial class AtmosphereSystem +{ + /* + Partial class for operations involving GasMixtures. + + Any method that is overridden here is usually because the server-sided implementation contains + code that would escape sandbox. As such these methods are overridden here with a safe + implementation. + */ + + /// + /// No-op on client as reactions aren't entirely in shared. + /// Don't call it. Smile. + public override ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) + { + return ReactionResult.NoReaction; + } + + public override bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) + { + var tmp = new float[Atmospherics.AdjustedNumberOfGases]; + NumericsHelpers.Multiply(mixture.Moles, GasFuelMask, tmp); + return NumericsHelpers.HorizontalAdd(tmp) > epsilon; + } + + public override bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) + { + var tmp = new float[Atmospherics.AdjustedNumberOfGases]; + NumericsHelpers.Multiply(mixture.Moles, GasOxidizerMask, tmp); + return NumericsHelpers.HorizontalAdd(tmp) > epsilon; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override float GetHeatCapacityCalculation(float[] moles, bool space) + { + // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms. + if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f)) + { + return Atmospherics.SpaceHeatCapacity; + } + + // explicit stackalloc call is banned on client tragically. + // the JIT does not stackalloc this during runtime, + // though this isnt the hottest code path so it should be fine + // the gc can eat a little as a treat + var tmp = new float[moles.Length]; + NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp); + // Adjust heat capacity by speedup, because this is primarily what + // determines how quickly gases heat up/cool. + return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); + } +} diff --git a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs index 44759372f4e..7d1545d0014 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosphereSystem.cs @@ -5,12 +5,13 @@ namespace Content.Client.Atmos.EntitySystems; -public sealed class AtmosphereSystem : SharedAtmosphereSystem +public sealed partial class AtmosphereSystem : SharedAtmosphereSystem // Adventure edit { public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapHandleState); + InitializeCVars(); // Adventure edit } private void OnMapHandleState(EntityUid uid, MapAtmosphereComponent component, ref ComponentHandleState args) diff --git a/Content.Client/Atmos/EntitySystems/GasTileFireOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileFireOverlaySystem.cs new file mode 100644 index 00000000000..06342903739 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasTileFireOverlaySystem.cs @@ -0,0 +1,30 @@ +using Content.Client.Atmos.Overlays; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// System responsible for rendering atmos fire animations using . +/// +[UsedImplicitly] +public sealed class GasTileFireOverlaySystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private GasTileFireOverlay _fireOverlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _fireOverlay = new GasTileFireOverlay(); + _overlayMan.AddOverlay(_fireOverlay); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMan.RemoveOverlay(); + } +} diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index ad264369467..6ea77394959 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,106 +1,91 @@ -using Content.Client.Atmos.Overlays; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; using Robust.Shared.GameStates; +using System.Linq; +using Robust.Shared.Timing; -namespace Content.Client.Atmos.EntitySystems +namespace Content.Client.Atmos.EntitySystems; + +[UsedImplicitly] +public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem { - [UsedImplicitly] - public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() { - [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IOverlayManager _overlayMan = default!; - [Dependency] private readonly SpriteSystem _spriteSys = default!; - [Dependency] private readonly SharedTransformSystem _xformSys = default!; + base.Initialize(); + SubscribeNetworkEvent(HandleGasOverlayUpdate); + SubscribeLocalEvent(OnHandleState); + } - private GasTileOverlay _overlay = default!; + private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) + { + Dictionary modifiedChunks; - public override void Initialize() + switch (args.Current) { - base.Initialize(); - SubscribeNetworkEvent(HandleGasOverlayUpdate); - SubscribeLocalEvent(OnHandleState); + // is this a delta or full state? + case GasTileOverlayDeltaState delta: + { + modifiedChunks = delta.ModifiedChunks; + foreach (var index in comp.Chunks.Keys) + { + if (!delta.AllChunks.Contains(index)) + comp.Chunks.Remove(index); + } + + break; + } + case GasTileOverlayState state: + { + modifiedChunks = state.Chunks; + foreach (var index in comp.Chunks.Keys.ToArray()) // Adventure edit + { + if (!state.Chunks.ContainsKey(index)) + comp.Chunks.Remove(index); + } - _overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys); - _overlayMan.AddOverlay(_overlay); + break; + } + default: + return; } - public override void Shutdown() + foreach (var (index, data) in modifiedChunks) { - base.Shutdown(); - _overlayMan.RemoveOverlay(); + comp.Chunks[index] = data; + data.LastUpdate = _timing.CurTick; } + } - private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) + private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev) + { + foreach (var (nent, removedIndicies) in ev.RemovedChunks) { - Dictionary modifiedChunks; - - switch (args.Current) - { - // is this a delta or full state? - case GasTileOverlayDeltaState delta: - { - modifiedChunks = delta.ModifiedChunks; - foreach (var index in comp.Chunks.Keys) - { - if (!delta.AllChunks.Contains(index)) - comp.Chunks.Remove(index); - } - - break; - } - case GasTileOverlayState state: - { - modifiedChunks = state.Chunks; - foreach (var index in comp.Chunks.Keys) - { - if (!state.Chunks.ContainsKey(index)) - comp.Chunks.Remove(index); - } + var grid = GetEntity(nent); - break; - } - default: - return; - } + if (!TryComp(grid, out GasTileOverlayComponent? comp)) + continue; - foreach (var (index, data) in modifiedChunks) + foreach (var index in removedIndicies) { - comp.Chunks[index] = data; + comp.Chunks.Remove(index); } } - private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev) + foreach (var (nent, gridData) in ev.UpdatedChunks) { - foreach (var (nent, removedIndicies) in ev.RemovedChunks) - { - var grid = GetEntity(nent); + var grid = GetEntity(nent); - if (!TryComp(grid, out GasTileOverlayComponent? comp)) - continue; + if (!TryComp(grid, out GasTileOverlayComponent? comp)) + continue; - foreach (var index in removedIndicies) - { - comp.Chunks.Remove(index); - } - } - - foreach (var (nent, gridData) in ev.UpdatedChunks) + foreach (var chunkData in gridData) { - var grid = GetEntity(nent); - - if (!TryComp(grid, out GasTileOverlayComponent? comp)) - continue; - - foreach (var chunkData in gridData) - { - comp.Chunks[chunkData.Index] = chunkData; - } + comp.Chunks[chunkData.Index] = chunkData; + chunkData.LastUpdate = _timing.CurTick; } } } diff --git a/Content.Client/Atmos/EntitySystems/GasTileVisibleGasOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileVisibleGasOverlaySystem.cs new file mode 100644 index 00000000000..b1548776534 --- /dev/null +++ b/Content.Client/Atmos/EntitySystems/GasTileVisibleGasOverlaySystem.cs @@ -0,0 +1,31 @@ +using Content.Client.Atmos.Overlays; +using JetBrains.Annotations; +using Robust.Client.Graphics; + +namespace Content.Client.Atmos.EntitySystems; + +/// +/// System responsible for rendering visible atmos gasses (like plasma for example) using . +/// +[UsedImplicitly] +public sealed class GasTileVisibleGasOverlaySystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private GasTileVisibleGasOverlay _visibleGasOverlay = default!; + + public override void Initialize() + { + base.Initialize(); + + _visibleGasOverlay = new GasTileVisibleGasOverlay(); + _overlayMan.AddOverlay(_visibleGasOverlay); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMan.RemoveOverlay(); + } + +} diff --git a/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs b/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs new file mode 100644 index 00000000000..d0e26208058 --- /dev/null +++ b/Content.Client/Atmos/Overlays/GasTileFireOverlay.cs @@ -0,0 +1,167 @@ +using Content.Client.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; +using Robust.Shared.Graphics.RSI; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using System.Numerics; + +namespace Content.Client.Atmos.Overlays; + +/// +/// Overlay responsible for rendering atmos fire animation. +/// +public sealed class GasTileFireOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; + private static readonly ProtoId UnshadedShader = "unshaded"; + + private readonly SharedTransformSystem _xformSys; + private readonly ShaderInstance _shader; + + private readonly float[] _timer; + private readonly float[][] _frameDelays; + private readonly int[] _frameCounter; + + // TODO combine textures into a single texture atlas. + private readonly Texture[][] _frames; + + private const int FireStates = 3; + private const string FireRsiPath = "/Textures/Effects/fire.rsi"; + + public const int GasOverlayZIndex = (int)Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else + + public GasTileFireOverlay() + { + IoCManager.InjectDependencies(this); + _xformSys = _entManager.System(); + _shader = _protoMan.Index(UnshadedShader).Instance(); + ZIndex = GasOverlayZIndex; + + _timer = new float[FireStates]; + _frameDelays = new float[FireStates][]; + _frameCounter = new int[FireStates]; + _frames = new Texture[FireStates][]; + + var fire = _resourceCache.GetResource(FireRsiPath).RSI; + + for (var i = 0; i < FireStates; i++) + { + if (!fire.TryGetState((i + 1).ToString(), out var state)) + throw new InvalidOperationException($"Fire RSI doesn't have state \"{i + 1}\"!"); + + _frames[i] = state.GetFrames(RsiDirection.South); + _frameDelays[i] = state.GetDelays(); + _frameCounter[i] = 0; + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + for (var i = 0; i < FireStates; i++) + { + var delays = _frameDelays[i]; + if (delays.Length == 0) + continue; + + var frameCount = _frameCounter[i]; + _timer[i] += args.DeltaSeconds; + var time = delays[frameCount]; + + if (_timer[i] < time) continue; + _timer[i] -= time; + _frameCounter[i] = (frameCount + 1) % _frames[i].Length; + } + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.MapId == MapId.Nullspace) + return; + + var drawHandle = args.WorldHandle; + var xformQuery = _entManager.GetEntityQuery(); + var overlayQuery = _entManager.GetEntityQuery(); + var gridState = (args.WorldBounds, + args.WorldHandle, + _frames, + _frameCounter, + _shader, + overlayQuery, + xformQuery, + _xformSys); + + if (args.Space != OverlaySpace.WorldSpaceEntities) + return; + + // TODO: WorldBounds callback. + _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, + static (EntityUid uid, MapGridComponent grid, + ref (Box2Rotated WorldBounds, + DrawingHandleWorld drawHandle, + Texture[][] frames, + int[] frameCounter, + ShaderInstance shader, + EntityQuery overlayQuery, + EntityQuery xformQuery, + SharedTransformSystem xformSys) state) => + { + if (!state.overlayQuery.TryGetComponent(uid, out var comp) || + !state.xformQuery.TryGetComponent(uid, out var gridXform)) + { + return true; + } + + var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform); + state.drawHandle.SetTransform(worldMatrix); + var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); + var localBounds = new Box2i( + (int)MathF.Floor(floatBounds.Left), + (int)MathF.Floor(floatBounds.Bottom), + (int)MathF.Ceiling(floatBounds.Right), + (int)MathF.Ceiling(floatBounds.Top)); + + // Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are + // ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls + // by chunk, even though its currently slower. + + state.drawHandle.UseShader(state.shader); + foreach (var chunk in comp.Chunks.Values) + { + var enumerator = new GasChunkEnumerator(chunk); + + while (enumerator.MoveNext(out var gas)) + { + if (gas.FireState == 0 || gas.FireState > FireStates) + continue; + + var index = chunk.Origin + (enumerator.X, enumerator.Y); + if (!localBounds.Contains(index)) + continue; + + var fireState = gas.FireState - 1; + var texture = state.frames[fireState][state.frameCounter[fireState]]; + state.drawHandle.DrawTexture(texture, index); + } + } + + return true; + }); + + drawHandle.UseShader(null); + drawHandle.SetTransform(Matrix3x2.Identity); + } +} diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs deleted file mode 100644 index eeb10b54d03..00000000000 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System.Numerics; -using Content.Client.Atmos.Components; -using Content.Client.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Components; -using Content.Shared.Atmos.EntitySystems; -using Content.Shared.Atmos.Prototypes; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.Enums; -using Robust.Shared.Graphics.RSI; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Client.Atmos.Overlays -{ - public sealed class GasTileOverlay : Overlay - { - private static readonly ProtoId UnshadedShader = "unshaded"; - - private readonly IEntityManager _entManager; - private readonly IMapManager _mapManager; - private readonly SharedAtmosphereSystem _atmosphereSystem; - private readonly SharedMapSystem _mapSystem; - private readonly SharedTransformSystem _xformSys; - - public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; - private readonly ShaderInstance _shader; - - // Gas overlays - private readonly float[] _timer; - private readonly float[][] _frameDelays; - private readonly int[] _frameCounter; - - // TODO combine textures into a single texture atlas. - private readonly Texture[][] _frames; - - // Fire overlays - private const int FireStates = 3; - private const string FireRsiPath = "/Textures/Effects/fire.rsi"; - - private readonly float[] _fireTimer = new float[FireStates]; - private readonly float[][] _fireFrameDelays = new float[FireStates][]; - private readonly int[] _fireFrameCounter = new int[FireStates]; - private readonly Texture[][] _fireFrames = new Texture[FireStates][]; - - private int _gasCount; - - public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else - - public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys) - { - _entManager = entManager; - _mapManager = IoCManager.Resolve(); - _atmosphereSystem = entManager.System(); - _mapSystem = entManager.System(); - _xformSys = xformSys; - _shader = protoMan.Index(UnshadedShader).Instance(); - ZIndex = GasOverlayZIndex; - - _gasCount = system.VisibleGasId.Length; - _timer = new float[_gasCount]; - _frameDelays = new float[_gasCount][]; - _frameCounter = new int[_gasCount]; - _frames = new Texture[_gasCount][]; - - for (var i = 0; i < _gasCount; i++) - { - var gasPrototype = _atmosphereSystem.GetGas(system.VisibleGasId[i]); - - SpriteSpecifier overlay; - - if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) - overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState); - else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture)) - overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture)); - else - continue; - - switch (overlay) - { - case SpriteSpecifier.Rsi animated: - var rsi = resourceCache.GetResource(animated.RsiPath).RSI; - var stateId = animated.RsiState; - - if (!rsi.TryGetState(stateId, out var state)) - continue; - - _frames[i] = state.GetFrames(RsiDirection.South); - _frameDelays[i] = state.GetDelays(); - _frameCounter[i] = 0; - break; - case SpriteSpecifier.Texture texture: - _frames[i] = new[] { spriteSys.Frame0(texture) }; - _frameDelays[i] = Array.Empty(); - break; - } - } - - var fire = resourceCache.GetResource(FireRsiPath).RSI; - - for (var i = 0; i < FireStates; i++) - { - if (!fire.TryGetState((i + 1).ToString(), out var state)) - throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!"); - - _fireFrames[i] = state.GetFrames(RsiDirection.South); - _fireFrameDelays[i] = state.GetDelays(); - _fireFrameCounter[i] = 0; - } - } - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - - for (var i = 0; i < _gasCount; i++) - { - var delays = _frameDelays[i]; - if (delays.Length == 0) - continue; - - var frameCount = _frameCounter[i]; - _timer[i] += args.DeltaSeconds; - var time = delays[frameCount]; - - if (_timer[i] < time) - continue; - - _timer[i] -= time; - _frameCounter[i] = (frameCount + 1) % _frames[i].Length; - } - - for (var i = 0; i < FireStates; i++) - { - var delays = _fireFrameDelays[i]; - if (delays.Length == 0) - continue; - - var frameCount = _fireFrameCounter[i]; - _fireTimer[i] += args.DeltaSeconds; - var time = delays[frameCount]; - - if (_fireTimer[i] < time) continue; - _fireTimer[i] -= time; - _fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length; - } - } - - protected override void Draw(in OverlayDrawArgs args) - { - if (args.MapId == MapId.Nullspace) - return; - - var drawHandle = args.WorldHandle; - var xformQuery = _entManager.GetEntityQuery(); - var overlayQuery = _entManager.GetEntityQuery(); - var gridState = (args.WorldBounds, - args.WorldHandle, - _gasCount, - _frames, - _frameCounter, - _fireFrames, - _fireFrameCounter, - _shader, - overlayQuery, - xformQuery, - _xformSys); - - var mapUid = _mapSystem.GetMapOrInvalid(args.MapId); - - if (_entManager.TryGetComponent(mapUid, out var atmos)) - DrawMapOverlay(drawHandle, args, mapUid, atmos); - - if (args.Space != OverlaySpace.WorldSpaceEntities) - return; - - // TODO: WorldBounds callback. - _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState, - static (EntityUid uid, MapGridComponent grid, - ref (Box2Rotated WorldBounds, - DrawingHandleWorld drawHandle, - int gasCount, - Texture[][] frames, - int[] frameCounter, - Texture[][] fireFrames, - int[] fireFrameCounter, - ShaderInstance shader, - EntityQuery overlayQuery, - EntityQuery xformQuery, - SharedTransformSystem xformSys) state) => - { - if (!state.overlayQuery.TryGetComponent(uid, out var comp) || - !state.xformQuery.TryGetComponent(uid, out var gridXform)) - { - return true; - } - - var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform); - state.drawHandle.SetTransform(worldMatrix); - var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); - var localBounds = new Box2i( - (int) MathF.Floor(floatBounds.Left), - (int) MathF.Floor(floatBounds.Bottom), - (int) MathF.Ceiling(floatBounds.Right), - (int) MathF.Ceiling(floatBounds.Top)); - - // Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are - // ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls - // by chunk, even though its currently slower. - - state.drawHandle.UseShader(null); - foreach (var chunk in comp.Chunks.Values) - { - var enumerator = new GasChunkEnumerator(chunk); - - while (enumerator.MoveNext(out var gas)) - { - if (gas.Opacity == null!) - continue; - - var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y); - if (!localBounds.Contains(tilePosition)) - continue; - - for (var i = 0; i < state.gasCount; i++) - { - var opacity = gas.Opacity[i]; - if (opacity > 0) - state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); - } - } - } - - // And again for fire, with the unshaded shader - state.drawHandle.UseShader(state.shader); - foreach (var chunk in comp.Chunks.Values) - { - var enumerator = new GasChunkEnumerator(chunk); - - while (enumerator.MoveNext(out var gas)) - { - if (gas.FireState == 0) - continue; - - var index = chunk.Origin + (enumerator.X, enumerator.Y); - if (!localBounds.Contains(index)) - continue; - - var fireState = gas.FireState - 1; - var texture = state.fireFrames[fireState][state.fireFrameCounter[fireState]]; - state.drawHandle.DrawTexture(texture, index); - } - } - - return true; - }); - - drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3x2.Identity); - } - - private void DrawMapOverlay( - DrawingHandleWorld handle, - OverlayDrawArgs args, - EntityUid map, - MapAtmosphereComponent atmos) - { - var mapGrid = _entManager.HasComponent(map); - - // map-grid atmospheres get drawn above grids - if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities) - return; - - // Normal map atmospheres get drawn below grids - if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld) - return; - - var bottomLeft = args.WorldAABB.BottomLeft.Floored(); - var topRight = args.WorldAABB.TopRight.Ceiled(); - - for (var x = bottomLeft.X; x <= topRight.X; x++) - { - for (var y = bottomLeft.Y; y <= topRight.Y; y++) - { - var tilePosition = new Vector2(x, y); - - for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) - { - var opacity = atmos.OverlayData.Opacity[i]; - - if (opacity > 0) - handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity)); - } - } - } - } - } -} diff --git a/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs new file mode 100644 index 00000000000..a4462536639 --- /dev/null +++ b/Content.Client/Atmos/Overlays/GasTileVisibleGasOverlay.cs @@ -0,0 +1,274 @@ +using Content.Client.Atmos.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; +using Robust.Shared.Graphics.RSI; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using System.Numerics; +using DrawDepth = Content.Shared.DrawDepth.DrawDepth; + +namespace Content.Client.Atmos.Overlays; + +/// +/// Overlay responsible for rendering visible atmos gasses (like plasma for example) +/// +public sealed class GasTileVisibleGasOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + /// Adventure edit: gas indices that should never be rendered (transparent gases) + private static readonly HashSet TransparentGases = new() + { + Gas.Oxygen, + Gas.Nitrogen, + Gas.CarbonDioxide, + Gas.NitrousOxide, + Gas.Hydrogen, + Gas.Helium, + Gas.Pluoxium, + Gas.HyperNoblium, + }; + + private readonly bool[] _isTransparent; + private readonly SharedAtmosphereSystem _atmosphereSystem; + private readonly SharedMapSystem _mapSystem; + private readonly SharedTransformSystem _xformSys; + private readonly SharedGasTileOverlaySystem _gasTileOverlaySystem; + private readonly SpriteSystem _spriteSystem; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld; + + // Gas overlays + private readonly float[] _timer; + private readonly float[][] _frameDelays; + private readonly int[] _frameCounter; + + // TODO combine textures into a single texture atlas. + private readonly Texture[][] _frames; + + private readonly int _gasCount; + + public const int GasOverlayZIndex = (int)DrawDepth.Gasses; // Under ghosts and fire, above mostly everything else + + public GasTileVisibleGasOverlay() + { + IoCManager.InjectDependencies(this); + _atmosphereSystem = _entManager.System(); + _mapSystem = _entManager.System(); + _xformSys = _entManager.System(); + _gasTileOverlaySystem = _entManager.System(); + _spriteSystem = _entManager.System(); + + ZIndex = GasOverlayZIndex; + + _gasCount = _gasTileOverlaySystem.VisibleGasId.Length; + _timer = new float[_gasCount]; + _frameDelays = new float[_gasCount][]; + _frameCounter = new int[_gasCount]; + _frames = new Texture[_gasCount][]; + _isTransparent = new bool[_gasCount]; + for (var i = 0; i < _gasCount; i++) + { + _isTransparent[i] = TransparentGases.Contains((Gas)_gasTileOverlaySystem.VisibleGasId[i]); + } + + for (var i = 0; i < _gasCount; i++) + { + var gasPrototype = _atmosphereSystem.GetGas(_gasTileOverlaySystem.VisibleGasId[i]); + + switch (gasPrototype.GasOverlaySprite) + { + case SpriteSpecifier.Rsi animated: + var rsi = _resourceCache.GetResource(animated.RsiPath).RSI; + var stateId = animated.RsiState; + + if (!rsi.TryGetState(stateId, out var state)) + { + // Adventure Edit + Logger.Error($"Gas overlay state '{stateId}' was not found in '{gasPrototype.GasOverlaySprite}'. Gas: {gasPrototype.ID}"); + _frames[i] = new[] { Texture.White }; + _frameDelays[i] = Array.Empty(); + break; + } + + _frames[i] = state.GetFrames(RsiDirection.South); + _frameDelays[i] = state.GetDelays(); + _frameCounter[i] = 0; + break; + case SpriteSpecifier.Texture texture: + _frames[i] = new[] { _spriteSystem.Frame0(texture) }; + _frameDelays[i] = Array.Empty(); + break; + } + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + for (var i = 0; i < _gasCount; i++) + { + var delays = _frameDelays[i]; + if (delays.Length == 0) + continue; + + var frameCount = _frameCounter[i]; + _timer[i] += args.DeltaSeconds; + var time = delays[frameCount]; + + if (_timer[i] < time) + continue; + + _timer[i] -= time; + _frameCounter[i] = (frameCount + 1) % _frames[i].Length; + } + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.MapId == MapId.Nullspace) + return; + + var drawHandle = args.WorldHandle; + var xformQuery = _entManager.GetEntityQuery(); + var overlayQuery = _entManager.GetEntityQuery(); + var gridState = (args.WorldBounds, + args.WorldHandle, + _gasCount, + _frames, + _frameCounter, + overlayQuery, + xformQuery, + _xformSys, + _isTransparent); + + var mapUid = _mapSystem.GetMapOrInvalid(args.MapId); + + if (_entManager.TryGetComponent(mapUid, out var atmos)) + { + DrawMapOverlay(drawHandle, args, mapUid, atmos); + } + + if (args.Space != OverlaySpace.WorldSpaceEntities) + return; + + // TODO: WorldBounds callback. + _mapManager.FindGridsIntersecting(args.MapId, + args.WorldAABB, + ref gridState, + static (EntityUid uid, + MapGridComponent grid, + ref (Box2Rotated WorldBounds, + DrawingHandleWorld drawHandle, + int gasCount, + Texture[][] frames, + int[] frameCounter, + EntityQuery overlayQuery, + EntityQuery xformQuery, + SharedTransformSystem xformSys, + bool[] isTransparent) state) => + { + if (!state.overlayQuery.TryGetComponent(uid, out var comp) || + !state.xformQuery.TryGetComponent(uid, out var gridXform)) + { + return true; + } + + var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform); + state.drawHandle.SetTransform(worldMatrix); + var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); + var localBounds = new Box2i( + (int)MathF.Floor(floatBounds.Left), + (int)MathF.Floor(floatBounds.Bottom), + (int)MathF.Ceiling(floatBounds.Right), + (int)MathF.Ceiling(floatBounds.Top)); + + // Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are + // ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls + // by chunk, even though its currently slower. + + foreach (var chunk in comp.Chunks.Values) + { + var enumerator = new GasChunkEnumerator(chunk); + + while (enumerator.MoveNext(out var gas)) + { + if (gas.Opacity == null!) + continue; + + var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y); + if (!localBounds.Contains(tilePosition)) + continue; + + for (var i = 0; i < state.gasCount; i++) + { + if (state.isTransparent[i]) + continue; + var opacity = gas.Opacity[i]; + if (opacity > 0) + { + state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], + tilePosition, + Color.White.WithAlpha(opacity / 255f * 0.6f)); + } + } + } + } + + return true; + }); + + drawHandle.SetTransform(Matrix3x2.Identity); + } + + private void DrawMapOverlay( + DrawingHandleWorld handle, + OverlayDrawArgs args, + EntityUid map, + MapAtmosphereComponent atmos) + { + var mapGrid = _entManager.HasComponent(map); + + // map-grid atmospheres get drawn above grids + if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities) + return; + + // Normal map atmospheres get drawn below grids + if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld) + return; + + var bottomLeft = args.WorldAABB.BottomLeft.Floored(); + var topRight = args.WorldAABB.TopRight.Ceiled(); + + for (var x = bottomLeft.X; x <= topRight.X; x++) + { + for (var y = bottomLeft.Y; y <= topRight.Y; y++) + { + var tilePosition = new Vector2(x, y); + + for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++) + { + if (_isTransparent[i]) + continue; + + var opacity = atmos.OverlayData.Opacity[i]; + + if (opacity > 0) + handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity / 255f * 0.6f)); + } + } + } + } +} diff --git a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs index 3a5df3f9a88..c8f2091d133 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerBoundUserInterface.cs @@ -1,41 +1,33 @@ -using Robust.Client.GameObjects; using Robust.Client.UserInterface; -using static Content.Shared.Atmos.Components.GasAnalyzerComponent; +using Content.Shared.Atmos.Components; -namespace Content.Client.Atmos.UI +namespace Content.Client.Atmos.UI; + +public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface { - public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface + [ViewVariables] + private GasAnalyzerWindow? _window; + + public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() { - [ViewVariables] - private GasAnalyzerWindow? _window; - - public GasAnalyzerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } - - protected override void Open() - { - base.Open(); - - _window = this.CreateWindowCenteredLeft(); - _window.OnClose += Close; - } - - protected override void ReceiveMessage(BoundUserInterfaceMessage message) - { - if (_window == null) - return; - if (message is not GasAnalyzerUserMessage cast) - return; - _window.Populate(cast); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - _window?.Dispose(); - } + base.Open(); + + _window = this.CreateWindowCenteredLeft(); + _window.OnClose += Close; + } + + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + if (_window == null) + return; + + if (message is not GasAnalyzerUserMessage cast) + return; + + _window.Populate(cast); } } diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs index 63b4e6b0c6f..94a9f237453 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs @@ -1,6 +1,8 @@ using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.EntitySystems; using Content.Shared.Temperature; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -8,7 +10,6 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; -using static Content.Shared.Atmos.Components.GasAnalyzerComponent; using Direction = Robust.Shared.Maths.Direction; namespace Content.Client.Atmos.UI @@ -16,25 +17,17 @@ namespace Content.Client.Atmos.UI [GenerateTypedNameReferences] public sealed partial class GasAnalyzerWindow : DefaultWindow { + private readonly SharedAtmosphereSystem _atmosphere; private NetEntity _currentEntity = NetEntity.Invalid; public GasAnalyzerWindow() { RobustXamlLoader.Load(this); + _atmosphere = IoCManager.Resolve().System(); } public void Populate(GasAnalyzerUserMessage msg) { - if (msg.Error != null) - { - CTopBox.AddChild(new Label - { - Text = Loc.GetString("gas-analyzer-window-error-text", ("errorText", msg.Error)), - FontColorOverride = Color.Red - }); - return; - } - if (msg.NodeGasMixes.Length == 0) { CTopBox.AddChild(new Label @@ -329,31 +322,31 @@ private void GenerateGasDisplay(GasMixEntry gasMix, Control parent) for (var j = 0; j < gasMix.Gases.Length; j++) { - var gas = gasMix.Gases[j]; - var color = Color.FromHex($"#{gas.Color}", Color.White); + var gasEntry = gasMix.Gases[j]; + var gasProto = _atmosphere.GetGas(gasEntry.Gas); // Add to the table tableKey.AddChild(new Label { - Text = Loc.GetString(gas.Name) + Text = Loc.GetString(gasProto.Name) }); tableVal.AddChild(new Label { Text = Loc.GetString("gas-analyzer-window-molarity-text", - ("mol", $"{gas.Amount:0.00}")), + ("mol", $"{gasEntry.Amount:0.00}")), Align = Label.AlignMode.Right, }); tablePercent.AddChild(new Label { Text = Loc.GetString("gas-analyzer-window-percentage-text", - ("percentage", $"{(gas.Amount / totalGasAmount * 100):0.0}")), + ("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.0}")), Align = Label.AlignMode.Right }); // Add to the gas bar //TODO: highlight the currently hover one - gasBar.AddEntry(gas.Amount, color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text", - ("gasName", gas.Name), - ("amount", $"{gas.Amount:0.##}"), - ("percentage", $"{(gas.Amount / totalGasAmount * 100):0.#}"))); + gasBar.AddEntry(gasEntry.Amount, gasProto.Color, tooltip: Loc.GetString("gas-analyzer-window-molarity-percentage-text", + ("gasName", Loc.GetString(gasProto.Name)), + ("amount", $"{gasEntry.Amount:0.##}"), + ("percentage", $"{(gasEntry.Amount / totalGasAmount * 100):0.#}"))); } dataContainer.AddChild(gasBar); diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs index 3392545be8a..b8b5350ff31 100644 --- a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs +++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs @@ -77,6 +77,12 @@ private void OnPrototypesReloaded(PrototypesReloadedEventArgs? ev) if (gas.Reagent == null) continue; + if (!_reagentSources.ContainsKey(gas.Reagent)) + { + Logger.Warning($"Reagent '{gas.Reagent}' referenced by gas '{gas.ID}' not found in reagent prototypes, skipping..."); + continue; + } + var data = new ReagentGasSourceData( new () { DefaultCondenseCategory }, gas); diff --git a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs index 6481e377c95..c95e168a010 100644 --- a/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs +++ b/Content.IntegrationTests/Tests/Atmos/ConstantsTest.cs @@ -27,16 +27,13 @@ await server.WaitPost(() => // number of gas prototypes Assert.That(gasProtos, Has.Count.EqualTo(Atmospherics.TotalNumberOfGases), - $"Number of GasPrototypes is not equal to TotalNumberOfGases."); + $"Number of GasPrototypes is not equal to TotalNumberOfGases."); // number of gas prototypes used in the atmos system Assert.That(atmosSystem.Gases.Count(), Is.EqualTo(Atmospherics.TotalNumberOfGases), - $"AtmosSystem.Gases is not equal to TotalNumberOfGases."); + $"AtmosSystem.Gases is not equal to TotalNumberOfGases."); // enum mapping gases to their Id Assert.That(Enum.GetValues(), Has.Length.EqualTo(Atmospherics.TotalNumberOfGases), - $"Gas enum size is not equal to TotalNumberOfGases."); - // localized abbreviations for UI purposes - Assert.That(Atmospherics.GasAbbreviations, Has.Count.EqualTo(Atmospherics.TotalNumberOfGases), - $"GasAbbreviations size is not equal to TotalNumberOfGases."); + $"Gas enum size is not equal to TotalNumberOfGases."); // the ID for each gas has to correspond to a value in the Gas enum (converted to a string) foreach (var gas in gasProtos) @@ -48,4 +45,3 @@ await server.WaitPost(() => await pair.CleanReturnAsync(); } } - diff --git a/Content.Server/Atmos/AtmosDirectionExtensions.cs b/Content.Server/Atmos/AtmosDirectionExtensions.cs new file mode 100644 index 00000000000..32470055c54 --- /dev/null +++ b/Content.Server/Atmos/AtmosDirectionExtensions.cs @@ -0,0 +1,54 @@ +using Content.Shared.Atmos; + +namespace Content.Server.Atmos; + +public static class AtmosDirectionExtensions +{ + public static Vector2i ToVec(this AtmosDirection direction) + { + return direction switch + { + AtmosDirection.North => new Vector2i(0, 1), + AtmosDirection.South => new Vector2i(0, -1), + AtmosDirection.East => new Vector2i(1, 0), + AtmosDirection.West => new Vector2i(-1, 0), + _ => Vector2i.Zero + }; + } + + public static AtmosDirection GetOpposite(this AtmosDirection direction) + { + return direction switch + { + AtmosDirection.North => AtmosDirection.South, + AtmosDirection.South => AtmosDirection.North, + AtmosDirection.East => AtmosDirection.West, + AtmosDirection.West => AtmosDirection.East, + _ => AtmosDirection.Invalid + }; + } + + public static int ToIndex(this AtmosDirection direction) + { + return direction switch + { + AtmosDirection.North => 0, + AtmosDirection.South => 1, + AtmosDirection.East => 2, + AtmosDirection.West => 3, + _ => -1 + }; + } + + public static int ToOppositeIndex(this int index) + { + return index switch + { + 0 => 1, + 1 => 0, + 2 => 3, + 3 => 2, + _ => -1 + }; + } +} diff --git a/Content.Server/Atmos/Components/IgniteOnAmmoHitComponent.cs b/Content.Server/Atmos/Components/IgniteOnAmmoHitComponent.cs new file mode 100644 index 00000000000..6eb3af60da1 --- /dev/null +++ b/Content.Server/Atmos/Components/IgniteOnAmmoHitComponent.cs @@ -0,0 +1,14 @@ +using Content.Server.Atmos.EntitySystems; + +namespace Content.Server.Atmos.Components; + +[RegisterComponent, Access(typeof(FlammableSystem))] +public sealed partial class IgniteOnAmmoHitComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), DataField("fireStacks")] + public float FireStacks; + + [ViewVariables(VVAccess.ReadWrite), DataField("fixtureId")] + public string FixtureId = "ignition"; + +} diff --git a/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs index 52f7f7f59ae..2370be29fd6 100644 --- a/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs +++ b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs @@ -2,7 +2,6 @@ using Content.Server.DeviceNetwork.Systems; using Content.Server.Pinpointer; using Content.Server.Power.Components; -using Content.Shared.PowerCell; // Adventure monitors using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Consoles; @@ -14,6 +13,16 @@ using Robust.Shared.Map.Components; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Power.EntitySystems; +using Content.Shared.Power.Components; +using Content.Shared.PowerCell; +using Content.Shared.UserInterface; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Content.Shared.PowerCell.Components; // Adventure-edit namespace Content.Server.Atmos.Monitor.Systems; @@ -27,7 +36,9 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly NavMapSystem _navMapSystem = default!; [Dependency] private readonly DeviceListSystem _deviceListSystem = default!; - [Dependency] private readonly PowerCellSystem _cell = default!; // Adventure monitors + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly PowerCellSystem _cell = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; private const float UpdateTime = 1.0f; @@ -49,6 +60,7 @@ public override void Initialize() // Alarm events SubscribeLocalEvent(OnDeviceTerminatingEvent); SubscribeLocalEvent(OnDeviceAnchorChanged); + } #region Event handling @@ -191,12 +203,40 @@ public override void Update(float frameTime) if (TryComp(ent, out var entAppearance)) _appearance.SetData(ent, AtmosAlertsComputerVisuals.ComputerLayerScreen, (int) highestAlert, entAppearance); + // Adventure-start + if (HasComp(ent) && HasComp(ent)) + { + if (_cell.HasActivatableCharge(ent) || _cell.HasDrawCharge(ent)) + { + Beep(ent, entConsole, highestAlert); + } + } + if (HasComp(ent)) + { + if (this.IsPowered(ent, EntityManager)) + { + Beep(ent, entConsole, highestAlert); + } + } + + // Adventure-end + // If the console UI is open, send UI data to each subscribed session UpdateUIState(ent, airAlarmEntries, fireAlarmEntries, entConsole, entXform); } } } + private void Beep(EntityUid ent, AtmosAlertsComputerComponent entConsole, AtmosAlarmType highestAlert) + { + if (entConsole.NextBeep >= _gameTiming.CurTime || highestAlert != AtmosAlarmType.Danger || + entConsole.BeepSound == null || !entConsole.DoAtmosAlert) // Adventure-edit + return; + + _audio.PlayPvs(entConsole.BeepSound, ent); + entConsole.NextBeep = _gameTiming.CurTime + entConsole.Timer; + } + public void UpdateUIState (EntityUid uid, AtmosAlertsComputerEntry[] airAlarmStateData, @@ -207,11 +247,6 @@ public void UpdateUIState if (!_userInterfaceSystem.IsUiOpen(uid, AtmosAlertsComputerUiKey.Key)) return; - // Adventure monitors start - if (!_cell.TryUseActivatableCharge(uid)) - return; - // Adventure monitors end - var gridUid = xform.GridUid!.Value; if (!HasComp(gridUid)) @@ -225,7 +260,7 @@ public void UpdateUIState // Set the UI state _userInterfaceSystem.SetUiState(uid, AtmosAlertsComputerUiKey.Key, - new AtmosAlertsComputerBoundInterfaceState(airAlarmStateData, fireAlarmStateData, focusAlarmData)); + new AtmosAlertsComputerBoundInterfaceState(airAlarmStateData, fireAlarmStateData, focusAlarmData, component.DoAtmosAlert)); } private List GetAlarmStateData(EntityUid gridUid, AtmosAlertsComputerGroup group) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs index f24f0ae171f..8c186568664 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.CVars.cs @@ -1,3 +1,4 @@ +using System; using Content.Shared.CCVar; using Robust.Shared.Configuration; @@ -25,7 +26,6 @@ public sealed partial class AtmosphereSystem public float AtmosMaxProcessTime { get; private set; } public float AtmosTickRate { get; private set; } public float Speedup { get; private set; } - public float HeatScale { get; private set; } public bool DeltaPressureDamage { get; private set; } public int DeltaPressureParallelProcessPerIteration { get; private set; } public int DeltaPressureParallelBatchSize { get; private set; } @@ -55,7 +55,16 @@ private void InitializeCVars() Subs.CVar(_cfg, CCVars.AtmosMaxProcessTime, value => AtmosMaxProcessTime = value, true); Subs.CVar(_cfg, CCVars.AtmosTickRate, value => AtmosTickRate = value, true); Subs.CVar(_cfg, CCVars.AtmosSpeedup, value => Speedup = value, true); - Subs.CVar(_cfg, CCVars.AtmosHeatScale, value => { HeatScale = value; InitializeGases(); }, true); + Subs.CVar(_cfg, CCVars.AtmosHeatScale, value => + { + if (value <= 0f) + { + Log.Error($"AtmosHeatScale must be greater than 0, but got {value}. Keeping previous value."); + return; + } + HeatScale = value; + InitializeGases(); + }, true); Subs.CVar(_cfg, CCVars.ExcitedGroups, value => ExcitedGroups = value, true); Subs.CVar(_cfg, CCVars.ExcitedGroupsSpaceIsAllConsuming, value => ExcitedGroupsSpaceIsAllConsuming = value, true); Subs.CVar(_cfg, CCVars.DeltaPressureDamage, value => DeltaPressureDamage = value, true); @@ -63,4 +72,4 @@ private void InitializeCVars() Subs.CVar(_cfg, CCVars.DeltaPressureParallelBatchSize, value => DeltaPressureParallelBatchSize = value, true); } } -} +} \ No newline at end of file diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index b4d7c643ce3..b5a70d47405 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -13,53 +13,23 @@ public sealed partial class AtmosphereSystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; - private GasReactionPrototype[] _gasReactions = Array.Empty(); - private float[] _gasSpecificHeats = new float[Atmospherics.TotalNumberOfGases]; + private GasReactionPrototype[] _gasReactions = []; /// /// List of gas reactions ordered by priority. /// public IEnumerable GasReactions => _gasReactions; - /// - /// Cached array of gas specific heats. - /// - public float[] GasSpecificHeats => _gasSpecificHeats; - - private void InitializeGases() + protected override void InitializeGases() { + base.InitializeGases(); + _gasReactions = _protoMan.EnumeratePrototypes().ToArray(); Array.Sort(_gasReactions, (a, b) => b.Priority.CompareTo(a.Priority)); - - Array.Resize(ref _gasSpecificHeats, MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4)); - - for (var i = 0; i < GasPrototypes.Length; i++) - { - _gasSpecificHeats[i] = GasPrototypes[i].SpecificHeat / HeatScale; - } - } - - /// - /// Calculates the heat capacity for a gas mixture. - /// - /// The mixture whose heat capacity should be calculated - /// Whether the internal heat capacity scaling should be applied. This should not be - /// used outside of atmospheric related heat transfer. - /// - public float GetHeatCapacity(GasMixture mixture, bool applyScaling) - { - var scale = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable); - - // By default GetHeatCapacityCalculation() has the heat-scale divisor pre-applied. - // So if we want the un-scaled heat capacity, we have to multiply by the scale. - return applyScaling ? scale : scale * HeatScale; } - private float GetHeatCapacity(GasMixture mixture) - => GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float GetHeatCapacityCalculation(float[] moles, bool space) + protected override float GetHeatCapacityCalculation(float[] moles, bool space) { // Little hack to make space gas mixtures have heat capacity, therefore allowing them to cool down rooms. if (space && MathHelper.CloseTo(NumericsHelpers.HorizontalAdd(moles), 0f)) @@ -71,31 +41,29 @@ private float GetHeatCapacityCalculation(float[] moles, bool space) NumericsHelpers.Multiply(moles, GasSpecificHeats, tmp); // Adjust heat capacity by speedup, because this is primarily what // determines how quickly gases heat up/cool. - return MathF.Max(NumericsHelpers.HorizontalAdd(tmp), Atmospherics.MinimumHeatCapacity); + return MathF.Max(NumericsHelpers.HorizontalAdd(tmp) / MathF.Max(HeatScale, 1e-6f), Atmospherics.MinimumHeatCapacity); // Adventure edit } - /// - /// Return speedup factor for pumped or flow-based devices that depend on MaxTransferRate. - /// - public float PumpSpeedup() + public override bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) { - return Speedup; + var tmp = new float[Atmospherics.AdjustedNumberOfGases]; + NumericsHelpers.Multiply(mixture.Moles, GasFuelMask, tmp); + return NumericsHelpers.HorizontalAdd(tmp) > epsilon; } - /// - /// Calculates the thermal energy for a gas mixture. - /// - public float GetThermalEnergy(GasMixture mixture) + public override bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) { - return mixture.Temperature * GetHeatCapacity(mixture); + var tmp = new float[Atmospherics.AdjustedNumberOfGases]; + NumericsHelpers.Multiply(mixture.Moles, GasOxidizerMask, tmp); + return NumericsHelpers.HorizontalAdd(tmp) > epsilon; } /// - /// Calculates the thermal energy for a gas mixture, using a cached heat capacity value. + /// Return speedup factor for pumped or flow-based devices that depend on MaxTransferRate. /// - public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity) + public float PumpSpeedup() { - return mixture.Temperature * cachedHeatCapacity; + return Speedup; } /// @@ -108,28 +76,6 @@ public void AddHeat(GasMixture mixture, float dQ) mixture.Temperature += dT; } - /// - /// Merges the gas mixture into the gas mixture. - /// The gas mixture is not modified by this method. - /// - public void Merge(GasMixture receiver, GasMixture giver) - { - if (receiver.Immutable) return; - - if (MathF.Abs(receiver.Temperature - giver.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider) - { - var receiverHeatCapacity = GetHeatCapacity(receiver); - var giverHeatCapacity = GetHeatCapacity(giver); - var combinedHeatCapacity = receiverHeatCapacity + giverHeatCapacity; - if (combinedHeatCapacity > Atmospherics.MinimumHeatCapacity) - { - receiver.Temperature = (GetThermalEnergy(giver, giverHeatCapacity) + GetThermalEnergy(receiver, receiverHeatCapacity)) / combinedHeatCapacity; - } - } - - NumericsHelpers.Add(receiver.Moles, giver.Moles); - } - /// /// Divides a source gas mixture into several recipient mixtures, scaled by their relative volumes. Does not /// modify the source gas mixture. Used for pipe network splitting. Note that the total destination volume @@ -358,6 +304,89 @@ 3. Expand both sides. return (-quadraticB + MathF.Sqrt(quadraticB * quadraticB - 4 * quadraticA * quadraticC)) / (2 * quadraticA); } + /// + /// Determines the fraction of gas to be removed and transferred from a source + /// to a target to reach a target pressure + /// in the target . + /// + /// The source that gas will be removed from. + /// This should always be of higher pressure than the second . + /// The target that will increase in pressure + /// to the target pressure. + /// The target mixture's desired pressure to target. + /// A float representing the dimensionless fraction of gas to transfer from the source + /// to the target. This may return negative if you have your mixtures swapped. + /// Note that this method doesn't take into account the heat capacity of the + /// transferred volume causing a pressure rise in the target . + [PublicAPI] + public static float FractionToMaxPressure(GasMixture mix1, GasMixture mix2, float targetPressure) + { + var molesToTransfer = MolesToMaxPressure(mix1, mix2, targetPressure); + return molesToTransfer / mix1.TotalMoles; + } + + /// + /// Determines the number of moles to be removed and transferred from a source + /// to a target to reach a target pressure + /// in the target . + /// + /// The source that gas will be removed from. + /// This should always be of higher pressure than the second . + /// The target that will increase in pressure + /// to the target pressure. + /// The target mixture's desired pressure to target. + /// The difference in moles required to reach the target pressure. + /// Note that this method doesn't take into account the heat capacity of the + /// transferred volume causing a pressure rise in the target . + [PublicAPI] + public static float MolesToMaxPressure(GasMixture mix1, GasMixture mix2, float targetPressure) + { + /* + Calculate the moles required to reach the target pressure. + The formula is derived from the ideal gas law and the + general Richman's law, under the simplification that all the specific heat capacities are equal. + Derivation can also be seen at + https://github.com/space-wizards/space-station-14/pull/35211/files/a0ae787fe07a4e792570f55b49d9dd8038eb6e4d#r1961183456 + TODO ATMOS Make this properly obey the heat capacity change on the target mixture. + + Derivation is as follows. + Assume A is mix1, B is mix2, C is the combined mixture after transfer. + We can express the number of moles in C: + n_C = n_A + n_B + + We can then determine the temperature of C: + T_C = \frac{T_A n_A c_A + T_B n_B c_B}{n_A c_A + n_B c_B} + + Where c_A and c_B are the specific heats of mixtures A and B, respectively. + We can then express the pressure of C: + P_C = \frac{n_C R T_C}{V_C} + + Using the above equations, we can express P_C as follows: + P_C = \frac{(n_A + n_B) R (\frac{T_a n_A + T_B n_B}{n_A + n_B}}{V_C} + + Which can be reduced to: + P_C = \frac{R (T_A n_A + T_B n_B)}{V_C} + + Solving for n_A gives: + n_A = \frac{P_C V_C - R T_B n_B}{R T_A} + + Using the ideal gas law to substitute: + n_A = \frac{P_C V_C - P_B V_B}{R T_A} + + The output volume doesn't change: + V_B = V_C + + So: + n_A = \frac{(P_C - P_B) V_B}{R T_A} + */ + + var delta = targetPressure - mix2.Pressure; + var requiredMoles = (delta * mix2.Volume) / (mix1.Temperature * Atmospherics.R); + + // Return the fraction of moles to transfer. + return requiredMoles; + } + /// /// Determines the number of moles that need to be removed from a to reach a target pressure threshold. /// @@ -439,38 +468,9 @@ public GasCompareResult CompareExchange(GasMixture sample, GasMixture otherSampl return GasCompareResult.NoExchange; } - /// - /// Performs reactions for a given gas mixture on an optional holder. - /// - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) + [PublicAPI] + public override ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) { - // Adventure hyper-noblium effect start - var hnMoles = mixture.GetMoles(Gas.HyperNoblium); - if (hnMoles > 0 && mixture.TotalMoles > Atmospherics.GasMinMoles) - { - var hnFraction = hnMoles / mixture.TotalMoles; - if (hnFraction >= Atmospherics.HyperNobliumFullSuppressionThresholdPercentage) - { - var savedResults = mixture.ReactionResults.ToArray(); - Array.Clear(mixture.ReactionResults, 0, mixture.ReactionResults.Length); - foreach (var prototype in GasReactions) - { - if (prototype.ID == "HyperNobliumProduction") - { - prototype.React(mixture, holder, this, HeatScale); - break; - } - } - for (int i = 0; i < savedResults.Length; i++) - { - mixture.ReactionResults[i] = 0; - } - - return ReactionResult.StopReactions; - } - } - // Adventure hyper-noblium effect end - var reaction = ReactionResult.NoReaction; var temperature = mixture.Temperature; var energy = GetThermalEnergy(mixture); @@ -525,6 +525,35 @@ public static void AddMolsToMixture(GasMixture mixture, ReadOnlySpan mols NumericsHelpers.Max(mixture.Moles, 0f); } + // ---- Adventure shim API ---- // + + public float GetThermalEnergy(GasMixture mixture) + => mixture.Temperature * GetHeatCapacity(mixture); + + public float GetThermalEnergy(GasMixture mixture, float cachedHeatCapacity) + => mixture.Temperature * cachedHeatCapacity; + + public void Merge(GasMixture receiver, GasMixture giver) + { + if (receiver.Immutable) + return; + + if (MathF.Abs(receiver.Temperature - giver.Temperature) > Atmospherics.MinimumTemperatureDeltaToConsider) + { + var rHC = GetHeatCapacity(receiver); + var gHC = GetHeatCapacity(giver); + var combined = rHC + gHC; + if (combined > Atmospherics.MinimumHeatCapacity) + receiver.Temperature = (GetThermalEnergy(receiver, rHC) + GetThermalEnergy(giver, gHC)) / combined; + } + NumericsHelpers.Add(receiver.Moles, giver.Moles); + } + + public bool IsMixtureIgnitable(GasMixture mixture) + => IsMixtureFuel(mixture) && IsMixtureOxidizer(mixture); + +// ---- End Adventure shim API ---- // + public enum GasCompareResult { NoExchange = -2, diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index f64212b2e77..67f19b3b0c3 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Content.Server.Atmos.Components; using Content.Server.Decals; using Content.Shared.Atmos; @@ -87,8 +88,7 @@ private void ProcessHotspot( if (tile.Hotspot.Temperature < Atmospherics.FireMinimumTemperatureToExist || tile.Hotspot.Volume <= 1f || tile.Air == null || - tile.Air.GetMoles(Gas.Oxygen) < 0.5f || - tile.Air.GetMoles(Gas.Plasma) < 0.5f && tile.Air.GetMoles(Gas.Tritium) < 0.5f && tile.Air.GetMoles(Gas.Hydrogen) < 0.5f) // Adventure gases + !IsMixtureIgnitable(tile.Air)) { tile.Hotspot = new Hotspot(); InvalidateVisuals(ent, tile); @@ -132,7 +132,7 @@ private void ProcessHotspot( cleanable: true); } - if (tile.Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread) + if (tile.Air?.Temperature > Atmospherics.FireMinimumTemperatureToSpread) { var radiatedTemperature = tile.Air.Temperature * Atmospherics.FireSpreadRadiosityScale; foreach (var otherTile in tile.AdjacentTiles) @@ -201,20 +201,16 @@ private void HotspotExpose(GridAtmosphereComponent gridAtmosphere, if (tile.Air == null) return; - var oxygen = tile.Air.GetMoles(Gas.Oxygen); - - if (oxygen < 0.5f) + if (!IsMixtureOxidizer(tile.Air)) return; - var plasma = tile.Air.GetMoles(Gas.Plasma); - var tritium = tile.Air.GetMoles(Gas.Tritium); - var hydrogen = tile.Air.GetMoles(Gas.Hydrogen); // Adventure gases + var isFlammable = IsMixtureIgnitable(tile.Air); if (tile.Hotspot.Valid) { if (soh) { - if (plasma > 0.5f || tritium > 0.5f || hydrogen > 0.5f) // Adventure gases + if (isFlammable) { tile.Hotspot.Temperature = MathF.Max(tile.Hotspot.Temperature, exposedTemperature); tile.Hotspot.Volume = MathF.Max(tile.Hotspot.Volume, exposedVolume); @@ -224,13 +220,14 @@ private void HotspotExpose(GridAtmosphereComponent gridAtmosphere, return; } - if (exposedTemperature > Atmospherics.PlasmaMinimumBurnTemperature && (plasma > 0.5f || tritium > 0.5f || hydrogen > 0.5f)) // Adventure gases + if (exposedTemperature > Atmospherics.PlasmaMinimumBurnTemperature && isFlammable) { if (sparkSourceUid.HasValue) { _adminLog.Add(LogType.Flammable, LogImpact.High, - $"Heat/spark of {ToPrettyString(sparkSourceUid.Value)} caused atmos ignition of gas: {tile.Air.Temperature.ToString():temperature}K - {oxygen}mol Oxygen, {plasma}mol Plasma, {tritium}mol Tritium"); + $"Heat/spark of {ToPrettyString(sparkSourceUid.Value)} caused atmos ignition of gas: " + + $"{tile.Air.ToPrettyString()}"); } tile.Hotspot = new Hotspot diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index d562fe51117..69ac9c86eb6 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -68,7 +68,13 @@ private bool ProcessRevalidate(Entity= AtmosMaxProcessTime) return false; @@ -209,6 +214,9 @@ private void UpdateTileData( (tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos); tile.MapAtmosphere = true; ent.Comp1.MapTiles.Add(tile); + // Adventure edit + UpdateAdjacentTiles(ent, tile, activate: true); + InvalidateVisuals(ent, tile); } DebugTools.AssertNotNull(tile.Air); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs index 9b53d0d16cb..4023315b364 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -25,7 +25,12 @@ public double GetPrice(GasMixture mixture) float maxComponent = 0; // moles of the dominant gas for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - basePrice += mixture.Moles[i] * GetGas(i).PricePerMole; + // Adventure edit start - gas price CVar modifiers + var gas = GetGas(i); + var modifier = GetModifier(gas.ID); + basePrice += mixture.Moles[i] * gas.PricePerMole * modifier; + // Adventure edit end + totalMoles += mixture.Moles[i]; maxComponent = Math.Max(maxComponent, mixture.Moles[i]); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs index df380912b6c..7ce35d2b700 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.cs @@ -16,6 +16,7 @@ using System.Linq; using Content.Shared.Damage.Systems; using Robust.Shared.Threading; +using System.Diagnostics; namespace Content.Server.Atmos.EntitySystems; @@ -73,6 +74,8 @@ public override void Initialize() SubscribeLocalEvent(OnPrototypesReloaded); CacheDecals(); + + InitADTAtmosCVars(); } public override void Shutdown() @@ -80,6 +83,8 @@ public override void Shutdown() base.Shutdown(); ShutdownCommands(); + + ShutdownADTAtmosCVars(); } private void OnTileChanged(ref TileChangedEvent ev) diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index c23f58637d7..386f0e00070 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -245,8 +245,6 @@ public override void Update(float frameTime) barotrauma.TakingDamage = true; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } - - _alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 2); } else if (pressure >= Atmospherics.HazardHighPressure) { @@ -260,8 +258,6 @@ public override void Update(float frameTime) barotrauma.TakingDamage = true; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } - - _alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 2); } else { diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index bd05a2c9ec1..e3af9775e99 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -24,6 +24,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Hands; using Content.Shared.Temperature.Components; +using Content.Shared.Weapons.Ranged.Events; using Robust.Server.Audio; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -433,14 +434,6 @@ public override void Update(float frameTime) flammable.FireStacks = MathF.Min(0, flammable.FireStacks + 1); } - if (!flammable.OnFire) - { - _alertsSystem.ClearAlert(uid, flammable.FireAlert); - continue; - } - - _alertsSystem.ShowAlert(uid, flammable.FireAlert); - if (flammable.FireStacks > 0) { var air = _atmosphereSystem.GetContainingMixture(uid); diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index 3cbe6575bf2..2a3ee3dc773 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -1,12 +1,10 @@ using System.Linq; using Content.Server.Atmos.Components; -using Content.Server.NodeContainer; using Content.Server.NodeContainer.Nodes; using Content.Server.Popups; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.NodeContainer; using JetBrains.Annotations; using Robust.Server.GameObjects; @@ -65,7 +63,7 @@ public override void Update(float frameTime) private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) { var target = args.Target; - if (target != null && !_interactionSystem.InRangeUnobstructed((args.User, null), (target.Value, null))) + if (target != null && !_interactionSystem.InRangeUnobstructed(args.User, target.Value)) // Adventure edit { target = null; // if the target is out of reach, invalidate it } @@ -146,7 +144,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul // Listen! Even if you don't want the Gas Analyzer to work on moving targets, you should use // this code to determine if the object is still generally in range so that the check is consistent with the code // in OnAfterInteract() and also consistent with interaction code in general. - if (!_interactionSystem.InRangeUnobstructed((component.User, null), (component.Target.Value, null))) + if (component.User == null || !_interactionSystem.InRangeUnobstructed(component.User.Value, component.Target.Value)) { if (component.User is { } userId && component.Enabled) _popup.PopupEntity(Loc.GetString("gas-analyzer-object-out-of-range"), userId, userId); @@ -159,16 +157,8 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul // Fetch the environmental atmosphere around the scanner. This must be the first entry var tileMixture = _atmo.GetContainingMixture(uid, true); - if (tileMixture != null) - { - gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature, - GenerateGasEntryArray(tileMixture))); - } - else - { - // No gases were found - gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f)); - } + var tileMixtureName = Loc.GetString("gas-analyzer-window-environment-tab-label"); + gasMixList.Add(GenerateGasMixEntry(tileMixtureName, tileMixture)); var deviceFlipped = false; if (component.Target != null) @@ -192,7 +182,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul { if (mixes.Item2 != null) { - gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2))); + gasMixList.Add(GenerateGasMixEntry(mixes.Item1, mixes.Item2)); validTarget = true; } } @@ -215,7 +205,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul var pipeAir = pipeNode.Air.Clone(); pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume); pipeAir.Volume = pipeNode.Volume; - gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir))); + gasMixList.Add(GenerateGasMixEntry(pair.Key, pipeAir)); validTarget = true; } } @@ -242,6 +232,23 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul return true; } + /// + /// Generates a GasMixEntry for a given GasMixture + /// + public GasMixEntry GenerateGasMixEntry(string name, GasMixture? mixture) + { + if (mixture == null) + return new GasMixEntry(name, 0, 0, 0); + + return new GasMixEntry( + name, + mixture.Volume, + mixture.Pressure, + mixture.Temperature, + GenerateGasEntryArray(mixture) + ); + } + /// /// Generates a GasEntry array for a given GasMixture /// @@ -259,7 +266,7 @@ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture) if (mixture != null) { var gasName = Loc.GetString(gas.Name); - gases.Add(new GasEntry(gasName, mixture[i], gas.Color)); + gases.Add(new GasEntry((Gas)i, mixture[i])); } } diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 4882e93d230..97f9036bb32 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,6 +1,3 @@ -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; using Content.Server.Atmos.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; @@ -15,24 +12,29 @@ using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.Enums; +using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Threading; using Robust.Shared.Timing; using Robust.Shared.Utility; +using System.Runtime.CompilerServices; // ReSharper disable once RedundantUsingDirective namespace Content.Server.Atmos.EntitySystems { + [UsedImplicitly] public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem { + [Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!; + private int _thermalDirtyThreshold = 1; + [Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!; [Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!; [Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!; - [Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _confMan = default!; [Robust.Shared.IoC.Dependency] private readonly IParallelManager _parMan = default!; [Robust.Shared.IoC.Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Robust.Shared.IoC.Dependency] private readonly ChunkingSystem _chunkingSys = default!; @@ -85,9 +87,8 @@ public override void Initialize() }; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - Subs.CVar(_confMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true); - Subs.CVar(_confMan, CCVars.GasOverlayThresholds, UpdateThresholds, true); - Subs.CVar(_confMan, CVars.NetPVS, OnPvsToggle, true); + + InitializeCVars(); SubscribeLocalEvent(Reset); SubscribeLocalEvent(OnStartup); @@ -175,7 +176,16 @@ private byte GetOpacity(float moles, float molesVisible, float molesVisibleMax) public GasOverlayData GetOverlayData(GasMixture? mixture) { - var data = new GasOverlayData(0, new byte[VisibleGasId.Length]); + ThermalByte byteTemp; + if (mixture == null) + { + byteTemp = new(); + byteTemp.SetVacuum(); + } + else + byteTemp = new(mixture.Temperature); + + var data = new GasOverlayData(0, new byte[VisibleGasId.Length], byteTemp); for (var i = 0; i < VisibleGasId.Length; i++) { @@ -215,15 +225,29 @@ private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayC } var changed = false; + + ThermalByte newByteTemp = new(); + + if (tile.Hotspot.Valid) + newByteTemp.SetTemperature(tile.Hotspot.Temperature); + else if (!tile.Space && tile.Air?.TotalMoles <= 5f) + newByteTemp.SetVacuum(); + else if (!tile.Space && tile.Air != null) + newByteTemp = new(tile.Air.Temperature); + if (oldData.Equals(default)) { changed = true; - oldData = new GasOverlayData(tile.Hotspot.State, new byte[VisibleGasId.Length]); + oldData = new GasOverlayData(tile.Hotspot.State, new byte[VisibleGasId.Length], newByteTemp); } - else if (oldData.FireState != tile.Hotspot.State) + else if ( + oldData.FireState != tile.Hotspot.State || + Math.Abs(newByteTemp.Value - oldData.ByteGasTemperature.Value) > _thermalDirtyThreshold || + (oldData.ByteGasTemperature.Value != newByteTemp.Value && + newByteTemp.Value > ThermalByte.TempResolution)) { changed = true; - oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity); + oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity, newByteTemp); } if (tile is {Air: not null, NoGridTile: false}) @@ -465,5 +489,13 @@ public void Execute(int index) } #endregion + + private void InitializeCVars() + { + Subs.CVar(ConfMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true); + Subs.CVar(ConfMan, CCVars.GasOverlayThresholds, UpdateThresholds, true); + Subs.CVar(ConfMan, CVars.NetPVS, OnPvsToggle, true); + Subs.CVar(_cfg, CCVars.GasOverlayThermalDirtyThreshold, v => _thermalDirtyThreshold = v, true); // Adventure-edit + } } } diff --git a/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs b/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs index a9aa40611dc..af180c5386c 100644 --- a/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs +++ b/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs @@ -79,6 +79,11 @@ public sealed partial class GasVentPumpComponent : Component /// public float ManualLockoutDisableDoAfter = 2.0f; + // Sunrtise-Start + [DataField] + public float PressureLimit { get; set; } = 300; + // Sunrtise-End + [DataField] public float ExternalPressureBound { diff --git a/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs b/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs index 4a9437bc1fc..f22f3057b9c 100644 --- a/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs +++ b/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs @@ -23,7 +23,7 @@ public sealed partial class GasVentScrubberComponent : Component public string OutletName { get; set; } = "pipe"; [DataField] - public HashSet FilterGases = new(GasVentScrubberData.DefaultFilterGases); + public HashSet FilterGases = [..GasVentScrubberData._defaultFilterGases]; [DataField] public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index 1066e4e88d3..861ec87a0d6 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -92,6 +92,24 @@ private void OnGasVentPumpUpdated(EntityUid uid, GasVentPumpComponent vent, ref { return; } + + // Adventure-Start + if (pipe.Air.Pressure > vent.PressureLimit) + { + // Release all gas like a passive vent + var inletAir = pipe.Air.RemoveRatio(1f); + var envAir = environment.RemoveRatio(1f); + + var mergeAir = new GasMixture(inletAir.Volume + envAir.Volume); + _atmosphereSystem.Merge(mergeAir, inletAir); + _atmosphereSystem.Merge(mergeAir, envAir); + + _atmosphereSystem.Merge(pipe.Air, mergeAir.RemoveVolume(inletAir.Volume)); + _atmosphereSystem.Merge(environment, mergeAir); + return; + } + // Adventure-End + // If the lockout has expired, disable it. if (vent.IsPressureLockoutManuallyDisabled && _timing.CurTime >= vent.ManualLockoutReenabledAt) { diff --git a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs index a295929aa2f..ea67667c46a 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs @@ -30,19 +30,17 @@ public sealed partial class PortableScrubberComponent : Component Gas.Ammonia, Gas.NitrousOxide, Gas.Frezon, - // Adventure gases begin Gas.BZ, - Gas.Halon, - Gas.Healium, - Gas.HyperNoblium, - Gas.Hydrogen, Gas.Pluoxium, + Gas.Hydrogen, Gas.Nitrium, + Gas.Healium, + Gas.HyperNoblium, + Gas.ProtoNitrate, + Gas.Zauker, + Gas.Halon, Gas.Helium, Gas.AntiNoblium, - Gas.ProtoNitrate, - Gas.Zauker - // Adventure gases end }; [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs b/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs index 475c149cf37..e4757b04cfe 100644 --- a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs +++ b/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs @@ -1,4 +1,4 @@ -using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; @@ -13,6 +13,11 @@ public sealed partial class FrezonCoolantReaction : IGasReactionEffect { public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) { + ///Adventure start + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + ///Adventure end var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); var temperature = mixture.Temperature; diff --git a/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs b/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs index e3d3ece6b6a..08d688f21aa 100644 --- a/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs +++ b/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs @@ -1,4 +1,4 @@ -using Content.Server.Atmos.EntitySystems; +using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; @@ -14,6 +14,11 @@ public sealed partial class FrezonProductionReaction : IGasReactionEffect { public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) { + ///Adventure start + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + ///Adventure end var initialN2 = mixture.GetMoles(Gas.Nitrogen); var initialOxy = mixture.GetMoles(Gas.Oxygen); var initialTrit = mixture.GetMoles(Gas.Tritium); diff --git a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs index 85802dc9575..dc7ba85dcc3 100644 --- a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs +++ b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs @@ -36,15 +36,6 @@ public sealed partial class GasReactionPrototype : IPrototype [DataField("minimumEnergy")] public float MinimumEnergyRequirement { get; private set; } = 0f; - /// Adventure gases start - /// - /// Maximum pressure requirement for the reaction to occur. - /// Defaults to unlimited pressure. - /// - [DataField("maximumPressure")] - public float MaximumPressureRequirement { get; private set; } = float.MaxValue; - /// Adventure gases end - /// /// Lower numbers are checked/react later than higher numbers. /// If two reactions have the same priority, they may happen in either order. @@ -66,11 +57,6 @@ public sealed partial class GasReactionPrototype : IPrototype /// Scaling factor that should be applied to all heat input or outputs. public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) { - // Adventure gases begin - // Проверка максимального давления - if (mixture.Pressure > MaximumPressureRequirement) - return ReactionResult.NoReaction; - // Adventure gases end var result = ReactionResult.NoReaction; foreach (var effect in _effects) diff --git a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs index b214310da12..f273bad1cfc 100644 --- a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs +++ b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs @@ -11,6 +11,11 @@ public sealed partial class PlasmaFireReaction : IGasReactionEffect { public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) { + //Adventure start + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + //Adventure end var energyReleased = 0f; var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); var temperature = mixture.Temperature; diff --git a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs index 8b7d4e4872e..7eecde30cb4 100644 --- a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs +++ b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs @@ -11,6 +11,11 @@ public sealed partial class TritiumFireReaction : IGasReactionEffect { public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) { + //Adventure start + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + //Adventure end var energyReleased = 0f; var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); var temperature = mixture.Temperature; diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index eba0df192a5..5d63a243d13 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -10,7 +10,7 @@ namespace Content.Server.Atmos; /// Use the public APIs in instead. /// [Access(typeof(AtmosphereSystem), typeof(GasTileOverlaySystem), typeof(AtmosDebugOverlaySystem))] -public sealed class TileAtmosphere : IGasMixtureHolder +public sealed partial class TileAtmosphere : IGasMixtureHolder // Adventure-edit { /// /// The last cycle this tile's air was archived into . @@ -245,4 +245,31 @@ public TileAtmosphere(TileAtmosphere other) public TileAtmosphere() { } + + // Adventure-Start + public void Reset() + { + Air = null; + AirArchived = null; + Array.Clear(AdjacentTiles, 0, AdjacentTiles.Length); + ExcitedGroup = null; + Hotspot = default; + GridIndex = EntityUid.Invalid; + GridIndices = default; + MapAtmosphere = false; + Space = false; + ArchivedCycle = 0; + LastShare = 0f; + MonstermosInfo = default; + CurrentCycle = 0; + Excited = false; + PressureDifference = 0f; + PressureDirection = AtmosDirection.Invalid; + PressureSpecificTarget = null; + MaxFireTemperatureSustained = 0f; + Temperature = 0f; + HeatCapacity = 0f; + AirtightData = default; + } + // Adventure-End } diff --git a/Content.Server/_Adventure/Atmos/Components/GridAtmosphereComponent.cs b/Content.Server/_Adventure/Atmos/Components/GridAtmosphereComponent.cs new file mode 100644 index 00000000000..ce93c7dc9dc --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Components/GridAtmosphereComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Atmos.Components +{ + public sealed partial class GridAtmosphereComponent + { + } +} diff --git a/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.CCVars.cs b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.CCVars.cs new file mode 100644 index 00000000000..1e9de476236 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.CCVars.cs @@ -0,0 +1,57 @@ +using Content.Shared._Adventure.ADTCCVars; +using Robust.Shared.Configuration; + +namespace Content.Server.Atmos.EntitySystems; + +public enum GasIds +{ + Tritium, + NitrousOxide, + Frezon, + BZ, + Healium, + Nitrium +} + +public partial class AtmosphereSystem +{ + private float _defaultGasPriceModifier; + private float _gasPriceModifierTritium; + private float _gasPriceModifierNitrousOxide; + private float _gasPriceModifierFrezon; + + private IDisposable? _configSub; + + public void InitADTAtmosCVars() + { + if (_configSub is not null) + { + return; + } + + _configSub = _cfg.SubscribeMultiple() + .OnValueChanged(ADTCCVars.DefaultGasPriceModifier, (value) => _defaultGasPriceModifier = value, true) + .OnValueChanged(ADTCCVars.GasPriceModifierTritium, (value) => _gasPriceModifierTritium = value, true) + .OnValueChanged(ADTCCVars.GasPriceModifierNitrousOxide, (value) => _gasPriceModifierNitrousOxide = value, true) + .OnValueChanged(ADTCCVars.GasPriceModifierFrezon, (value) => _gasPriceModifierFrezon = value, true); + } + + public float GetModifier(string id) + { + if (!Enum.TryParse(id, out var gasId)) + return _defaultGasPriceModifier; + + return gasId switch + { + GasIds.Tritium => _gasPriceModifierTritium, + GasIds.NitrousOxide => _gasPriceModifierNitrousOxide, + GasIds.Frezon => _gasPriceModifierFrezon, + _ => _defaultGasPriceModifier, + }; + } + + private void ShutdownADTAtmosCVars() + { + _configSub?.Dispose(); + } +} diff --git a/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.Processing.cs new file mode 100644 index 00000000000..7ebfd63321f --- /dev/null +++ b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -0,0 +1,10 @@ +using Content.Server.Atmos.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Robust.Shared.Map.Components; + +namespace Content.Server.Atmos.EntitySystems; + +public sealed partial class AtmosphereSystem +{ +} diff --git a/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.cs b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.cs new file mode 100644 index 00000000000..b0586654046 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/EntitySystems/AtmosphereSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Atmos.Components; +using Content.Server.Electrocution; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Mobs.Components; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; + +namespace Content.Server.Atmos.EntitySystems; + +public sealed partial class AtmosphereSystem +{ + [Dependency] private readonly ElectrocutionSystem _electrocution = default!; + [Dependency] private readonly BatterySystem _battery = default!; + + private EntityQuery _powerReceiverQuery; + private EntityQuery _mobQuery; + private EntityQuery _batteryQuery; +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/BZProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/BZProductionReaction.cs new file mode 100644 index 00000000000..3838e9f4d2b --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/BZProductionReaction.cs @@ -0,0 +1,51 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class BZProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialNitrousOxide = mixture.GetMoles(Gas.NitrousOxide); + var initialPlasma = mixture.GetMoles(Gas.Plasma); + + var environmentEfficiency = mixture.Volume / mixture.Pressure; + var ratioEfficiency = Math.Min(initialNitrousOxide / initialPlasma, 1); + + var BZFormed = Math.Min(0.01f * ratioEfficiency * environmentEfficiency, Math.Min(initialNitrousOxide * 0.4f, initialPlasma * 0.8f)); + + if (initialNitrousOxide - BZFormed * 0.4f < 0 || initialPlasma - (0.8f - BZFormed) < 0 || BZFormed <= 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + var amountDecomposed = 0.0f; + var nitrousOxideDecomposedFactor = Math.Max(4.0f * (initialPlasma / (initialNitrousOxide + initialPlasma) - 0.75f), 0); + if (nitrousOxideDecomposedFactor > 0) + { + amountDecomposed = 0.4f * BZFormed * nitrousOxideDecomposedFactor; + mixture.AdjustMoles(Gas.Oxygen, amountDecomposed); + mixture.AdjustMoles(Gas.Nitrogen, 0.5f * amountDecomposed); + } + + mixture.AdjustMoles(Gas.BZ, Math.Max(0f, BZFormed * (1.0f - nitrousOxideDecomposedFactor))); + mixture.AdjustMoles(Gas.NitrousOxide, -0.4f * BZFormed); + mixture.AdjustMoles(Gas.Plasma, -0.8f * BZFormed * (1.0f - nitrousOxideDecomposedFactor)); + + var energyReleased = BZFormed * (Atmospherics.BZFormationEnergy + nitrousOxideDecomposedFactor * (Atmospherics.NitrousOxideDecompositionEnergy - Atmospherics.BZFormationEnergy)); + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/HalonOxygenAbsorptionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/HalonOxygenAbsorptionReaction.cs new file mode 100644 index 00000000000..624cf1963b9 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/HalonOxygenAbsorptionReaction.cs @@ -0,0 +1,39 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class HalonOxygenAbsorptionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialHalon = mixture.GetMoles(Gas.Halon); + var initialOxygen = mixture.GetMoles(Gas.Oxygen); + + var temperature = mixture.Temperature; + + var heatEfficiency = Math.Min(temperature / (Atmospherics.FireMinimumTemperatureToExist * 10f), Math.Min(initialHalon, initialOxygen * 20f)); + if (heatEfficiency <= 0f || initialHalon - heatEfficiency < 0f || initialOxygen - heatEfficiency * 20f < 0f) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Halon, -heatEfficiency); + mixture.AdjustMoles(Gas.Oxygen, -heatEfficiency * 20f); + mixture.AdjustMoles(Gas.CarbonDioxide, heatEfficiency * 5f); + + var energyUsed = heatEfficiency * Atmospherics.HalonCombustionEnergy; + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyUsed) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/HealiumProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/HealiumProductionReaction.cs new file mode 100644 index 00000000000..64be7a35333 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/HealiumProductionReaction.cs @@ -0,0 +1,40 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class HealiumProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialBZ = mixture.GetMoles(Gas.BZ); + var initialFrezon = mixture.GetMoles(Gas.Frezon); + + var temperature = mixture.Temperature; + var heatEfficiency = Math.Min(temperature * 0.3f, Math.Min(initialFrezon * 2.75f, initialBZ * 0.25f)); + + if (heatEfficiency <= 0 || initialFrezon - heatEfficiency * 2.75f < 0 || initialBZ - heatEfficiency * 0.25f < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Frezon, -heatEfficiency * 2.75f); + mixture.AdjustMoles(Gas.BZ, -heatEfficiency * 0.25f); + mixture.AdjustMoles(Gas.Healium, heatEfficiency * 3); + + var energyReleased = heatEfficiency * Atmospherics.HealiumFormationEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/HydrogenFireReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/HydrogenFireReaction.cs new file mode 100644 index 00000000000..6e7c6532646 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/HydrogenFireReaction.cs @@ -0,0 +1,59 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions +{ + [UsedImplicitly] + [DataDefinition] + public sealed partial class HydrogenFireReaction : IGasReactionEffect + { + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var energyReleased = 0f; + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + var temperature = mixture.Temperature; + var location = holder as TileAtmosphere; + mixture.ReactionResults[(byte)GasReaction.Fire] = 0; + + var initialOxygen = mixture.GetMoles(Gas.Oxygen); + var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); + + var burnedFuel = Math.Min(initialHydrogen / Atmospherics.FireH2BurnRateDelta, Math.Min(initialOxygen / (Atmospherics.FireH2BurnRateDelta * Atmospherics.H2OxygenFullBurn), Math.Min(initialHydrogen, initialOxygen * 0.5f))); + + if (burnedFuel > 0) + { + energyReleased += Atmospherics.FireH2EnergyReleased * burnedFuel; + + mixture.AdjustMoles(Gas.WaterVapor, burnedFuel); + mixture.AdjustMoles(Gas.Hydrogen, -burnedFuel); + mixture.AdjustMoles(Gas.Oxygen, -burnedFuel * 0.5f); + + mixture.ReactionResults[(byte)GasReaction.Fire] += burnedFuel; + } + + if (energyReleased > 0) + { + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = (temperature * oldHeatCapacity + energyReleased) / newHeatCapacity; + } + + if (location != null) + { + temperature = mixture.Temperature; + if (temperature > Atmospherics.FireMinimumTemperatureToExist) + { + atmosphereSystem.HotspotExpose(location, temperature, mixture.Volume); + } + } + + return mixture.ReactionResults[(byte)GasReaction.Fire] != 0 ? ReactionResult.Reacting : ReactionResult.NoReaction; + } + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/HyperNobliumProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/HyperNobliumProductionReaction.cs new file mode 100644 index 00000000000..441bb8453bf --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/HyperNobliumProductionReaction.cs @@ -0,0 +1,41 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class HyperNobliumProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); + var initialTritium = mixture.GetMoles(Gas.Tritium); + var initialBZ = mixture.GetMoles(Gas.BZ); + + var nobFormed = Math.Min((initialNitrogen + initialTritium) * 0.01f, Math.Min(initialTritium * 5f, initialNitrogen * 10f)); + if (nobFormed <= 0 || (initialTritium - 5f) * nobFormed < 0 || (initialNitrogen - 10f) * nobFormed < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + var reductionFactor = Math.Clamp(initialTritium / (initialTritium + initialBZ), 0.001f, 1f); + + mixture.AdjustMoles(Gas.Tritium, -5f * nobFormed * reductionFactor); + mixture.AdjustMoles(Gas.Nitrogen, -10f * nobFormed); + mixture.AdjustMoles(Gas.HyperNoblium, nobFormed); + + var energyReleased = nobFormed * (Atmospherics.NobliumFormationEnergy / Math.Max(initialBZ, 1)); + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/NitriumDecompositionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/NitriumDecompositionReaction.cs new file mode 100644 index 00000000000..540bb2671c0 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/NitriumDecompositionReaction.cs @@ -0,0 +1,39 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class NitriumDecompositionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialNitrium = mixture.GetMoles(Gas.Nitrium); + + var temperature = mixture.Temperature; + var heatEfficiency = Math.Min(temperature / Atmospherics.NitriumDecompositionTempDivisor, initialNitrium); + + if (heatEfficiency <= 0 || initialNitrium - heatEfficiency < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Nitrium, -heatEfficiency); + mixture.AdjustMoles(Gas.Hydrogen, heatEfficiency); + mixture.AdjustMoles(Gas.Nitrogen, heatEfficiency); + + var energyReleased = heatEfficiency * Atmospherics.NitriumDecompositionEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/NitriumProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/NitriumProductionReaction.cs new file mode 100644 index 00000000000..96519be6660 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/NitriumProductionReaction.cs @@ -0,0 +1,41 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class NitriumProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialTritium = mixture.GetMoles(Gas.Tritium); + var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); + var initialBZ = mixture.GetMoles(Gas.BZ); + + var temperature = mixture.Temperature; + var heatEfficiency = Math.Min(temperature / Atmospherics.NitriumFormationTempDivisor, Math.Min(initialTritium, Math.Min(initialNitrogen, initialBZ * 0.05f))); + + if (heatEfficiency <= 0 || initialTritium - heatEfficiency < 0 || initialNitrogen - heatEfficiency < 0 || initialBZ - heatEfficiency * 0.05f < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + mixture.AdjustMoles(Gas.Tritium, -heatEfficiency); + mixture.AdjustMoles(Gas.Nitrogen, -heatEfficiency); + mixture.AdjustMoles(Gas.BZ, -heatEfficiency * 0.05f); + mixture.AdjustMoles(Gas.Nitrium, heatEfficiency); + + var energyUsed = heatEfficiency * Atmospherics.NitriumFormationEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity - energyUsed) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/PluoxiumProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/PluoxiumProductionReaction.cs new file mode 100644 index 00000000000..4c02418a208 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/PluoxiumProductionReaction.cs @@ -0,0 +1,41 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class PluoxiumProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialCarbonDioxide = mixture.GetMoles(Gas.CarbonDioxide); + var initialOxygen = mixture.GetMoles(Gas.Oxygen); + var initialTritium = mixture.GetMoles(Gas.Tritium); + + var producedAmount = Math.Min(Atmospherics.PluoxiumMaxRate, Math.Min(initialCarbonDioxide, Math.Min(initialOxygen * 0.5f, initialTritium * 0.01f))); + + if (producedAmount <= 0 || initialCarbonDioxide - producedAmount < 0 || initialOxygen - producedAmount * 0.5f < 0 || initialTritium - producedAmount * 0.01f < 0) + return ReactionResult.NoReaction; + + mixture.AdjustMoles(Gas.CarbonDioxide, -producedAmount); + mixture.AdjustMoles(Gas.Oxygen, -producedAmount * 0.5f); + mixture.AdjustMoles(Gas.Tritium, -producedAmount * 0.01f); + mixture.AdjustMoles(Gas.Pluoxium, producedAmount); + mixture.AdjustMoles(Gas.Hydrogen, producedAmount * 0.01f); + + var energyReleased = producedAmount * Atmospherics.PluoxiumFormationEnergy; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateBZaseConversionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateBZaseConversionReaction.cs new file mode 100644 index 00000000000..8d89f1965f9 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateBZaseConversionReaction.cs @@ -0,0 +1,41 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ProtoNitrateBZaseConversionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialProtoNitrate = mixture.GetMoles(Gas.ProtoNitrate); + var initialBZ = mixture.GetMoles(Gas.BZ); + + var temperature = mixture.Temperature; + var consumedAmount = Math.Min(temperature / 2240f * initialBZ * initialProtoNitrate / (initialBZ + initialProtoNitrate), Math.Min(initialBZ, initialProtoNitrate)); + + if (consumedAmount <= 0 || initialBZ - consumedAmount < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.BZ, -consumedAmount); + mixture.AdjustMoles(Gas.Nitrogen, consumedAmount * 0.4f); + mixture.AdjustMoles(Gas.Helium, consumedAmount * 1.6f); + mixture.AdjustMoles(Gas.Plasma, consumedAmount * 0.8f); + + var energyReleased = consumedAmount * Atmospherics.ProtoNitrateBZaseConversionEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateHydrogenConversionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateHydrogenConversionReaction.cs new file mode 100644 index 00000000000..bd01cd6ca14 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateHydrogenConversionReaction.cs @@ -0,0 +1,38 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ProtoNitrateHydrogenConversionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialProtoNitrate = mixture.GetMoles(Gas.ProtoNitrate); + var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); + + var producedAmount = Math.Min(Atmospherics.ProtoNitrateHydrogenConversionMaxRate, Math.Min(initialHydrogen, initialProtoNitrate)); + + if (producedAmount <= 0 || initialHydrogen-producedAmount < 0f) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Hydrogen, -producedAmount); + mixture.AdjustMoles(Gas.ProtoNitrate, producedAmount * 0.5f); + + var energyUsed = producedAmount * Atmospherics.ProtoNitrateHydrogenConversionEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity - energyUsed) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateProductionReaction.cs new file mode 100644 index 00000000000..43c9b49eca4 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateProductionReaction.cs @@ -0,0 +1,40 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ProtoNitrateProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialPluoxium = mixture.GetMoles(Gas.Pluoxium); + var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); + + var temperature = mixture.Temperature; + var heatEfficiency = Math.Min(temperature * 0.005f, Math.Min(initialPluoxium * 0.2f, initialHydrogen * 2.0f)); + + if (heatEfficiency <= 0 || initialPluoxium - heatEfficiency * 0.2f < 0 || initialHydrogen - heatEfficiency * 2f < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Hydrogen, -heatEfficiency * 2f); + mixture.AdjustMoles(Gas.Pluoxium, -heatEfficiency * 0.2f); + mixture.AdjustMoles(Gas.ProtoNitrate, heatEfficiency * 0.2f); + + var energyReleased = heatEfficiency * Atmospherics.ProtoNitrateFormationEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateTritiumConversionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateTritiumConversionReaction.cs new file mode 100644 index 00000000000..3f873b8a1d2 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ProtoNitrateTritiumConversionReaction.cs @@ -0,0 +1,40 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ProtoNitrateTritiumConversionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialProtoNitrate = mixture.GetMoles(Gas.ProtoNitrate); + var initialTritium = mixture.GetMoles(Gas.Tritium); + + var temperature = mixture.Temperature; + var producedAmount = Math.Min(temperature / 34f * initialTritium * initialProtoNitrate/ (initialTritium + 10f * initialProtoNitrate), Math.Min(initialTritium, initialProtoNitrate * 0.01f)); + + if (initialTritium - producedAmount < 0 || initialProtoNitrate - producedAmount * 0.01f < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.ProtoNitrate, -producedAmount * 0.01f); + mixture.AdjustMoles(Gas.Tritium, -producedAmount); + mixture.AdjustMoles(Gas.Hydrogen, producedAmount); + + var energyReleased = producedAmount * Atmospherics.ProtoNitrateTritiumConversionEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ZaukerDecompositionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ZaukerDecompositionReaction.cs new file mode 100644 index 00000000000..5f93393419d --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ZaukerDecompositionReaction.cs @@ -0,0 +1,39 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ZaukerDecompositionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialZauker = mixture.GetMoles(Gas.Zauker); + var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); + + var burnedFuel = Math.Min(Atmospherics.ZaukerDecompositionMaxRate, Math.Min(initialNitrogen, initialZauker)); + + if (burnedFuel <= 0 || initialZauker - burnedFuel < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.Zauker, -burnedFuel); + mixture.AdjustMoles(Gas.Oxygen, burnedFuel * 0.3f); + mixture.AdjustMoles(Gas.Nitrogen, burnedFuel * 0.7f); + + var energyReleased = burnedFuel * Atmospherics.ZaukerDecompositionEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/Reactions/ZaukerProductionReaction.cs b/Content.Server/_Adventure/Atmos/Reactions/ZaukerProductionReaction.cs new file mode 100644 index 00000000000..c1e101bcb15 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/Reactions/ZaukerProductionReaction.cs @@ -0,0 +1,40 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using JetBrains.Annotations; + +namespace Content.Server.Atmos.Reactions; + +[UsedImplicitly] +public sealed partial class ZaukerProductionReaction : IGasReactionEffect +{ + public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) + { + var initialHyperNoblium = mixture.GetMoles(Gas.HyperNoblium); + if (initialHyperNoblium >= 5.0f && mixture.Temperature > 20f) + return ReactionResult.NoReaction; + + var initialHypernoblium = mixture.GetMoles(Gas.HyperNoblium); + var initialNitrium = mixture.GetMoles(Gas.Nitrium); + + var temperature = mixture.Temperature; + var heatEfficiency = Math.Min(temperature * Atmospherics.ZaukerFormationTemperatureScale, Math.Min(initialHypernoblium * 0.01f, initialNitrium * 0.5f)); + + if (heatEfficiency <= 0 || initialHypernoblium - heatEfficiency * 0.01f < 0 || initialNitrium - heatEfficiency * 0.5f < 0) + return ReactionResult.NoReaction; + + var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + + mixture.AdjustMoles(Gas.HyperNoblium, -heatEfficiency * 0.01f); + mixture.AdjustMoles(Gas.Nitrium, -heatEfficiency * 0.5f); + mixture.AdjustMoles(Gas.Zauker, heatEfficiency * 0.5f); + + var energyUsed = heatEfficiency * Atmospherics.ZaukerFormationEnergy; + + var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); + if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) + mixture.Temperature = Math.Max((mixture.Temperature * oldHeatCapacity - energyUsed) / newHeatCapacity, Atmospherics.TCMB); + + return ReactionResult.Reacting; + } +} diff --git a/Content.Server/_Adventure/Atmos/TileAtmosphere.cs b/Content.Server/_Adventure/Atmos/TileAtmosphere.cs new file mode 100644 index 00000000000..bb26f89f379 --- /dev/null +++ b/Content.Server/_Adventure/Atmos/TileAtmosphere.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Atmos; +public sealed partial class TileAtmosphere +{ +} diff --git a/Content.Server/_Adventure/Atmospherics/BZProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/BZProductionReaction.cs deleted file mode 100644 index e111589f700..00000000000 --- a/Content.Server/_Adventure/Atmospherics/BZProductionReaction.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Синтез БЗ из плазмы и оксида азота. -/// Имеется лимит по давлению, если превышает 40КПа, реакция прекращается. -/// -[UsedImplicitly] -public sealed partial class BZProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Pressure > Atmospherics.BZSynthesisMaxPressure) - { - return ReactionResult.NoReaction; - } - var initialPlasma = mixture.GetMoles(Gas.Plasma); - var initialN20 = mixture.GetMoles(Gas.NitrousOxide); - - if (initialPlasma <= 0 || initialN20 <= 0) - return ReactionResult.NoReaction; - - var plasmaLimit = initialPlasma / Atmospherics.BZPlasmaRatio; - var n20Limit = initialN20 / Atmospherics.BZN20Ratio; - var limitingFactor = Math.Min(plasmaLimit, n20Limit); - - if (limitingFactor <= 0) - return ReactionResult.NoReaction; - - limitingFactor = Math.Min(limitingFactor, Atmospherics.BZSynthesisMaxRate); - - var plasmaBurned = limitingFactor * Atmospherics.BZPlasmaRatio; - var n20Burned = limitingFactor * Atmospherics.BZN20Ratio; - var bzProduced = (plasmaBurned + n20Burned) * Atmospherics.BZSynthesisEfficiency; - - mixture.AdjustMoles(Gas.Plasma, -plasmaBurned); - mixture.AdjustMoles(Gas.NitrousOxide, -n20Burned); - mixture.AdjustMoles(Gas.BZ, bzProduced); - - var energyToAdd = bzProduced * Atmospherics.BZFormationEnergy / heatScale; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyToAdd / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/HalonFireSuppressionReaction.cs b/Content.Server/_Adventure/Atmospherics/HalonFireSuppressionReaction.cs deleted file mode 100644 index 35f628c46d7..00000000000 --- a/Content.Server/_Adventure/Atmospherics/HalonFireSuppressionReaction.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; -/// -/// Реакция поглощения кислорода и тепла галоном при нагревании. -/// -[UsedImplicitly] -public sealed partial class HalonFireSuppressionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.HalonActivationTemperature) - return ReactionResult.NoReaction; - - var initialHalon = mixture.GetMoles(Gas.Halon); - var initialOxygen = mixture.GetMoles(Gas.Oxygen); - - if (initialHalon <= 0 || initialOxygen <= 0) - return ReactionResult.NoReaction; - - var temperatureFactor = MathHelper.Clamp( - (mixture.Temperature - Atmospherics.HalonActivationTemperature) / - (Atmospherics.HalonMaxTemperature - Atmospherics.HalonActivationTemperature), - 0f, 1f); - - var absorptionRate = initialHalon * temperatureFactor * Atmospherics.HalonAbsorptionRate; - - var oxygenAbsorbed = Math.Min(absorptionRate, initialOxygen); - var heatAbsorbed = oxygenAbsorbed * Atmospherics.HalonHeatAbsorptionFactor; - - mixture.AdjustMoles(Gas.Halon, -oxygenAbsorbed * 0.1f); - mixture.AdjustMoles(Gas.Oxygen, -oxygenAbsorbed); - mixture.AdjustMoles(Gas.CarbonDioxide, oxygenAbsorbed * 0.8f); - - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature -= heatAbsorbed / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/HealiumProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/HealiumProductionReaction.cs deleted file mode 100644 index 74777ae4c76..00000000000 --- a/Content.Server/_Adventure/Atmospherics/HealiumProductionReaction.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Реакция синтеза хилиума из фрезона и BZ -/// Соотношение: 1 Frezon + 1 BZ → 0.25 Healium -/// Ограничения: температура, максимальная скорость синтеза -/// -[UsedImplicitly] -public sealed partial class HealiumProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.HealiumMinTemperature || - mixture.Temperature > Atmospherics.HealiumMaxTemperature) - return ReactionResult.NoReaction; - - var initialFrezon = mixture.GetMoles(Gas.Frezon); - var initialBZ = mixture.GetMoles(Gas.BZ); - - var availableMix = (initialFrezon + initialBZ) / Atmospherics.HealiumConversionRatio; - var limitingFactor = Math.Min(availableMix, Math.Min(initialFrezon, initialBZ)); - - if (limitingFactor <= 0) - return ReactionResult.NoReaction; - - limitingFactor = Math.Min(limitingFactor, Atmospherics.HealiumProductionMaxRate); - - var frezonBurned = limitingFactor; - var bzBurned = limitingFactor; - var healiumProduced = limitingFactor * Atmospherics.HealiumProductionYield; - - mixture.AdjustMoles(Gas.Frezon, -frezonBurned); - mixture.AdjustMoles(Gas.BZ, -bzBurned); - mixture.AdjustMoles(Gas.Healium, healiumProduced); - - var temperatureFactor = MathHelper.Clamp( - (mixture.Temperature - Atmospherics.HealiumMinTemperature) / - (Atmospherics.HealiumMaxTemperature - Atmospherics.HealiumMinTemperature), - 0.5f, 2f); - - var energyReleased = healiumProduced * Atmospherics.HealiumProductionEnergy * temperatureFactor / heatScale; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyReleased / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/HydrogenBurnReaction.cs b/Content.Server/_Adventure/Atmospherics/HydrogenBurnReaction.cs deleted file mode 100644 index 7135b2e5317..00000000000 --- a/Content.Server/_Adventure/Atmospherics/HydrogenBurnReaction.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions -/// -/// Реакция сгорания водорода в кислородной среде. -/// -{ - [UsedImplicitly] - [DataDefinition] - public sealed partial class HydrogenBurnReaction : IGasReactionEffect - { - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var energyReleased = 0f; - var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - var temperature = mixture.Temperature; - var location = holder as TileAtmosphere; - - mixture.ReactionResults[(byte)GasReaction.Fire] = 0f; - - var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); - var initialOxygen = mixture.GetMoles(Gas.Oxygen); - - // Минимальное соотношение H2:O2 = 2:1 (по стехиометрии) - var burnRatio = Atmospherics.HydrogenBurnRate; - - var burnedFuel = Math.Min( - initialHydrogen * burnRatio, - initialOxygen * Atmospherics.HydrogenBurnOxyFactor // 1 моль O2 требуется на 2 моля H2 - ); - - if (burnedFuel <= 0f) - return ReactionResult.NoReaction; - - mixture.AdjustMoles(Gas.Hydrogen, -burnedFuel); - mixture.AdjustMoles(Gas.Oxygen, -burnedFuel * 0.5f); - - energyReleased += burnedFuel * Atmospherics.FireHydrogenEnergyReleased; - - mixture.ReactionResults[(byte)GasReaction.Fire] += burnedFuel; - - energyReleased /= heatScale; - - if (energyReleased > 0) - { - var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature = ((temperature * oldHeatCapacity + energyReleased) / newHeatCapacity); - } - - if (location != null && mixture.Temperature > Atmospherics.FireMinimumTemperatureToExist) - { - atmosphereSystem.HotspotExpose(location, mixture.Temperature, mixture.Volume); - } - - return ReactionResult.Reacting; - } - } -} diff --git a/Content.Server/_Adventure/Atmospherics/HyperNobliumEffectSystem.cs b/Content.Server/_Adventure/Atmospherics/HyperNobliumEffectSystem.cs deleted file mode 100644 index 41cd106fac3..00000000000 --- a/Content.Server/_Adventure/Atmospherics/HyperNobliumEffectSystem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Adventure.Atmos.Reactions; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Эффект газа гиперноблиум - полное подавление всех химических реакций при наличии ≥5 молей -/// -[UsedImplicitly] -public sealed partial class HypernobliumEffect : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var hyperNobliumMoles = mixture.GetMoles(Gas.HyperNoblium); - var totalMoles = mixture.TotalMoles; - - if (totalMoles < Atmospherics.GasMinMoles) - return ReactionResult.NoReaction; - - var fraction = hyperNobliumMoles / totalMoles; - - if (fraction >= Atmospherics.HyperNobliumFullSuppressionThresholdPercentage) - { - Array.Clear(mixture.ReactionResults, 0, mixture.ReactionResults.Length); - return ReactionResult.StopReactions; - } - - return ReactionResult.NoReaction; - } -} - diff --git a/Content.Server/_Adventure/Atmospherics/HyperNobliumProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/HyperNobliumProductionReaction.cs deleted file mode 100644 index 8e3b001cd9f..00000000000 --- a/Content.Server/_Adventure/Atmospherics/HyperNobliumProductionReaction.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; -/// -/// Реакция синтеза гипер-ноблиума из азота и трития -/// Соотношение: 10 азота + 5 трития → 1 гипер-ноблиума -/// -[UsedImplicitly] -public sealed partial class HyperNobliumProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.HyperNobliumProductionMinTemp || - mixture.Temperature > Atmospherics.HyperNobliumProductionMaxTemp) - return ReactionResult.NoReaction; - - var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); - var initialTritium = mixture.GetMoles(Gas.Tritium); - var initialBZ = mixture.GetMoles(Gas.BZ); - - var totalGas = initialTritium + initialBZ; - var tritiumReductionFactor = totalGas > 0 - ? Math.Clamp(initialTritium / totalGas, 0.001f, 1f) - : 1f; - - var nobliumPossible = Math.Min( - (initialNitrogen + initialTritium) * 0.01f, - Math.Min( - initialTritium / (Atmospherics.HyperNobliumProductionTritiumRatio * tritiumReductionFactor), - initialNitrogen / Atmospherics.HyperNobliumProductionNitrogenRatio - ) - ); - - nobliumPossible = Math.Min(nobliumPossible, Atmospherics.HyperNobliumProductionMaxRate); - - if (nobliumPossible <= 0 || - initialTritium < Atmospherics.HyperNobliumProductionTritiumRatio * nobliumPossible * tritiumReductionFactor || - initialNitrogen < Atmospherics.HyperNobliumProductionNitrogenRatio * nobliumPossible) - { - return ReactionResult.NoReaction; - } - - mixture.AdjustMoles(Gas.Nitrogen, -Atmospherics.HyperNobliumProductionNitrogenRatio * nobliumPossible); - mixture.AdjustMoles(Gas.Tritium, -Atmospherics.HyperNobliumProductionTritiumRatio * nobliumPossible * tritiumReductionFactor); - mixture.AdjustMoles(Gas.HyperNoblium, nobliumPossible); - - var energyReleased = nobliumPossible * - (Atmospherics.HyperNobliumProductionEnergy / - Math.Max(initialBZ, 1f)) / heatScale; - - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - { - mixture.Temperature = Math.Max( - (mixture.Temperature * heatCapacity + energyReleased) / heatCapacity, - Atmospherics.TCMB - ); - } - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/NitriumDecompositionReaction.cs b/Content.Server/_Adventure/Atmospherics/NitriumDecompositionReaction.cs deleted file mode 100644 index 6ce173c5ae7..00000000000 --- a/Content.Server/_Adventure/Atmospherics/NitriumDecompositionReaction.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Разложение нитриума в кислородной среде. -/// -[UsedImplicitly] -public sealed partial class NitriumDecompositionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature > Atmospherics.NitriumDecompositionMaxTemp) - return ReactionResult.NoReaction; - - var initialNitrium = mixture.GetMoles(Gas.Nitrium); - if (initialNitrium <= 0) - return ReactionResult.NoReaction; - - var efficiency = Math.Min( - mixture.Temperature / Atmospherics.NitriumDecompositionTempDivisor, - initialNitrium - ); - - var decomposedAmount = efficiency * Atmospherics.NitriumDecompositionRate; - mixture.AdjustMoles(Gas.Nitrium, -decomposedAmount); - mixture.AdjustMoles(Gas.Nitrogen, decomposedAmount); - mixture.AdjustMoles(Gas.Hydrogen, decomposedAmount); - - var energyReleased = decomposedAmount * Atmospherics.NitriumDecompositionEnergy; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyReleased / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/NitriumProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/NitriumProductionReaction.cs deleted file mode 100644 index cea85990a9c..00000000000 --- a/Content.Server/_Adventure/Atmospherics/NitriumProductionReaction.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions -{ - [UsedImplicitly] - [DataDefinition] - public sealed partial class NitriumProductionReaction : IGasReactionEffect - { - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var initialTritium = mixture.GetMoles(Gas.Tritium); - var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); - var initialBZ = mixture.GetMoles(Gas.BZ); - - if (initialTritium < Atmospherics.NitriumMinTritium || - initialNitrogen < Atmospherics.NitriumMinNitrogen || - initialBZ < Atmospherics.NitriumMinBZ || - mixture.Temperature < Atmospherics.NitriumProductionMinTemp) - { - return ReactionResult.NoReaction; - } - - var productionEfficiency = Math.Min( - mixture.Temperature / Atmospherics.NitriumProductionTempDivisor, - Math.Min( - initialTritium / Atmospherics.NitriumTritiumRatio, - Math.Min( - initialNitrogen / Atmospherics.NitriumNitrogenRatio, - initialBZ / Atmospherics.NitriumBZRatio - ) - ) - ); - - productionEfficiency = Math.Min(productionEfficiency, Atmospherics.NitriumProductionMaxRate); - - if (productionEfficiency <= 0f || - initialTritium - productionEfficiency * Atmospherics.NitriumTritiumRatio < 0f || - initialNitrogen - productionEfficiency * Atmospherics.NitriumNitrogenRatio < 0f || - initialBZ - productionEfficiency * Atmospherics.NitriumBZRatio < 0f) - { - return ReactionResult.NoReaction; - } - - var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - var temperature = mixture.Temperature; - - mixture.AdjustMoles(Gas.Tritium, -productionEfficiency * Atmospherics.NitriumTritiumRatio); - mixture.AdjustMoles(Gas.Nitrogen, -productionEfficiency * Atmospherics.NitriumNitrogenRatio); - mixture.AdjustMoles(Gas.BZ, -productionEfficiency * Atmospherics.NitriumBZRatio); - mixture.AdjustMoles(Gas.Nitrium, productionEfficiency); - - var energyConsumed = productionEfficiency * Atmospherics.NitriumProductionEnergy / heatScale; - - if (energyConsumed > 0f) - { - var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) - { - mixture.Temperature = Math.Max( - (temperature * oldHeatCapacity - energyConsumed) / newHeatCapacity, - Atmospherics.TCMB - ); - } - } - - return ReactionResult.Reacting; - } - } -} diff --git a/Content.Server/_Adventure/Atmospherics/PluoxiumProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/PluoxiumProductionReaction.cs deleted file mode 100644 index a8a2c80fdad..00000000000 --- a/Content.Server/_Adventure/Atmospherics/PluoxiumProductionReaction.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Реакция синтеза плюоксиума из диоксида, кислорода и трития. -/// Соотношение: 1 CO2 + 0.5 O2 + 0.01 трития → 1 плюоксиума + 0.01 H2 + Энергия -/// -[UsedImplicitly] -[DataDefinition] -public sealed partial class PluoxiumProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var initialCO2 = mixture.GetMoles(Gas.CarbonDioxide); - var initialO2 = mixture.GetMoles(Gas.Oxygen); - var initialTritium = mixture.GetMoles(Gas.Tritium); - var producedAmount = Math.Min( - Atmospherics.PluoxiumFormationMaxRate, - Math.Min( - initialCO2, - Math.Min( - initialO2 / Atmospherics.PluoxiumOxygenRatio, - initialTritium / Atmospherics.PluoxiumTritiumRatio - ) - ) - ); - if (producedAmount <= 0 || - initialCO2 - producedAmount < 0 || - initialO2 - producedAmount * Atmospherics.PluoxiumOxygenRatio < 0 || - initialTritium - producedAmount * Atmospherics.PluoxiumTritiumRatio < 0) - { - return ReactionResult.NoReaction; - } - var oldHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - var temperature = mixture.Temperature; - mixture.AdjustMoles(Gas.CarbonDioxide, -producedAmount); - mixture.AdjustMoles(Gas.Oxygen, -producedAmount * Atmospherics.PluoxiumOxygenRatio); - mixture.AdjustMoles(Gas.Tritium, -producedAmount * Atmospherics.PluoxiumTritiumRatio); - mixture.AdjustMoles(Gas.Pluoxium, producedAmount); - mixture.AdjustMoles(Gas.Hydrogen, producedAmount * Atmospherics.PluoxiumHydrogenByproductRatio); - var energyReleased = producedAmount * Atmospherics.PluoxiumFormationEnergy / heatScale; - if (energyReleased > 0) - { - var newHeatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (newHeatCapacity > Atmospherics.MinimumHeatCapacity) - { - mixture.Temperature = Math.Max( - (temperature * oldHeatCapacity + energyReleased) / newHeatCapacity, - Atmospherics.TCMB - ); - } - } - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ProtoNitrateBZDecomposition.cs b/Content.Server/_Adventure/Atmospherics/ProtoNitrateBZDecomposition.cs deleted file mode 100644 index c3448dd865b..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ProtoNitrateBZDecomposition.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Разложение БЗ с помощью прото-нитрата. -/// -[UsedImplicitly] -public sealed partial class ProtoNitrateBZDecomposition : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.PNBZaseMinTemp || - mixture.Temperature > Atmospherics.PNBZaseMaxTemp) - return ReactionResult.NoReaction; - - var initialBZ = mixture.GetMoles(Gas.BZ); - var initialPN = mixture.GetMoles(Gas.ProtoNitrate); - - if (initialBZ <= 0 || initialPN <= 0) - return ReactionResult.NoReaction; - - var reactionRate = Math.Min( - initialBZ, - initialPN * Atmospherics.PNBZaseConversionRate - ); - - mixture.AdjustMoles(Gas.BZ, -reactionRate); - mixture.AdjustMoles(Gas.Nitrogen, reactionRate * 0.4f); - mixture.AdjustMoles(Gas.Helium, reactionRate * 1.6f); - mixture.AdjustMoles(Gas.Plasma, reactionRate * 0.8f); - - var energyReleased = reactionRate * Atmospherics.PNBZaseEnergy; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyReleased / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ProtoNitrateHydrogenConversion.cs b/Content.Server/_Adventure/Atmospherics/ProtoNitrateHydrogenConversion.cs deleted file mode 100644 index e94ac4398cd..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ProtoNitrateHydrogenConversion.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Конверсия водорода c участием прото-нитрата. -/// -[UsedImplicitly] -public sealed partial class ProtoNitrateHydrogenConversion : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); - var initialPN = mixture.GetMoles(Gas.ProtoNitrate); - - if (initialHydrogen < Atmospherics.PNHydrogenConversionThreshold || initialPN <= 0) - return ReactionResult.NoReaction; - - var conversionRate = Math.Min( - Math.Min(initialHydrogen, initialPN * Atmospherics.PNHydrogenConversionRate), - Atmospherics.PNHydrogenConversionMaxRate - ); - - mixture.AdjustMoles(Gas.Hydrogen, -conversionRate); - mixture.AdjustMoles(Gas.ProtoNitrate, conversionRate * 0.5f); - - var energyConsumed = conversionRate * Atmospherics.PNHydrogenConversionEnergy; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature = Math.Max( - mixture.Temperature - energyConsumed / heatCapacity, - Atmospherics.TCMB - ); - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ProtoNitrateProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/ProtoNitrateProductionReaction.cs deleted file mode 100644 index b205ce4906f..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ProtoNitrateProductionReaction.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Производство прото-нитрата из плюоксиума и водорода. -/// Требует высоких температур (5000K-10000K) и имеет температурный коэффициент. -/// -[UsedImplicitly] -public sealed partial class ProtoNitrateProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.PNProductionMinTemperature || - mixture.Temperature > Atmospherics.PNProductionMaxTemperature) - { - return ReactionResult.NoReaction; - } - - var initialPluoxium = mixture.GetMoles(Gas.Pluoxium); - var initialHydrogen = mixture.GetMoles(Gas.Hydrogen); - - if (initialPluoxium <= 0 || initialHydrogen <= 0) - return ReactionResult.NoReaction; - - var temperatureFactor = mixture.Temperature * Atmospherics.PNProductionTemperatureScale; - - var pluoxiumLimit = initialPluoxium / Atmospherics.PNPluoxiumRatio; - var hydrogenLimit = initialHydrogen / Atmospherics.PNHydrogenRatio; - var limitingFactor = Math.Min(pluoxiumLimit, hydrogenLimit); - - if (limitingFactor <= 0) - return ReactionResult.NoReaction; - - var reactionRate = Math.Min(limitingFactor, temperatureFactor); - - var pluoxiumBurned = reactionRate * Atmospherics.PNPluoxiumRatio; - var hydrogenBurned = reactionRate * Atmospherics.PNHydrogenRatio; - - var protoNitrateProduced = (pluoxiumBurned + hydrogenBurned) * Atmospherics.PNProductionEfficiency; - - mixture.AdjustMoles(Gas.Pluoxium, -pluoxiumBurned); - mixture.AdjustMoles(Gas.Hydrogen, -hydrogenBurned); - mixture.AdjustMoles(Gas.ProtoNitrate, protoNitrateProduced); - - var energyToAdd = protoNitrateProduced * Atmospherics.PNProductionEnergy / heatScale; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyToAdd / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ProtoNitrateTritiumReaction.cs b/Content.Server/_Adventure/Atmospherics/ProtoNitrateTritiumReaction.cs deleted file mode 100644 index e54f5f1b705..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ProtoNitrateTritiumReaction.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Детоксикация трития с помощью прото-нитрата. -/// -[UsedImplicitly] -public sealed partial class ProtoNitrateTritiumReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.PNTritiumMinTemp || - mixture.Temperature > Atmospherics.PNTritiumMaxTemp) - return ReactionResult.NoReaction; - - var initialTritium = mixture.GetMoles(Gas.Tritium); - var initialPN = mixture.GetMoles(Gas.ProtoNitrate); - - if (initialTritium <= 0 || initialPN <= 0) - return ReactionResult.NoReaction; - - var reactionRate = Math.Min( - initialTritium, - initialPN * Atmospherics.PNTritiumConversionRate - ); - - mixture.AdjustMoles(Gas.Tritium, -reactionRate); - mixture.AdjustMoles(Gas.Hydrogen, reactionRate); - mixture.AdjustMoles(Gas.ProtoNitrate, -reactionRate * 0.01f); - - var energyReleased = reactionRate * Atmospherics.PNTritiumConversionEnergy; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyReleased / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ZaukerDecompositionReaction.cs b/Content.Server/_Adventure/Atmospherics/ZaukerDecompositionReaction.cs deleted file mode 100644 index c42bbe4c7d7..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ZaukerDecompositionReaction.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Разложение заукера в азотной среде. -/// -[UsedImplicitly] -public sealed partial class ZaukerDecompositionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - var initialZauker = mixture.GetMoles(Gas.Zauker); - var initialNitrogen = mixture.GetMoles(Gas.Nitrogen); - - if (initialZauker <= 0 || initialNitrogen <= 0) - return ReactionResult.NoReaction; - - var decomposedAmount = Math.Min( - Math.Min(initialZauker, initialNitrogen), - Atmospherics.ZaukerDecompositionMaxRate - ); - - mixture.AdjustMoles(Gas.Zauker, -decomposedAmount); - mixture.AdjustMoles(Gas.Nitrogen, decomposedAmount * 0.7f); - mixture.AdjustMoles(Gas.Oxygen, decomposedAmount * 0.3f); - - var energyReleased = decomposedAmount * Atmospherics.ZaukerDecompositionEnergy; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature += energyReleased / heatCapacity; - - return ReactionResult.Reacting; - } -} diff --git a/Content.Server/_Adventure/Atmospherics/ZaukerProductionReaction.cs b/Content.Server/_Adventure/Atmospherics/ZaukerProductionReaction.cs deleted file mode 100644 index ac7db988b86..00000000000 --- a/Content.Server/_Adventure/Atmospherics/ZaukerProductionReaction.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Reactions; -using JetBrains.Annotations; - -namespace Content.Server.Adventure.Atmos.Reactions; - -/// -/// Производство заукера из гипер-ноблиума и нитриума. -/// Требует экстремально высоких температур (50000K-75000K). -/// Максимальное производство ограничено 5 молями за тик. -/// -[UsedImplicitly] -public sealed partial class ZaukerProductionReaction : IGasReactionEffect -{ - public ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder, AtmosphereSystem atmosphereSystem, float heatScale) - { - if (mixture.Temperature < Atmospherics.ZaukerProductionMinTemperature || - mixture.Temperature > Atmospherics.ZaukerProductionMaxTemperature) - { - return ReactionResult.NoReaction; - } - - var initialHyperNob = mixture.GetMoles(Gas.HyperNoblium); - var initialNitrium = mixture.GetMoles(Gas.Nitrium); - - if (initialHyperNob <= 0 || initialNitrium <= 0) - return ReactionResult.NoReaction; - - var temperatureFactor = mixture.Temperature * Atmospherics.ZaukerProductionTemperatureScale; - - var hyperNobLimit = initialHyperNob / Atmospherics.ZaukerHyperNobRatio; - var nitriumLimit = initialNitrium / Atmospherics.ZaukerNitriumRatio; - var limitingFactor = Math.Min(hyperNobLimit, nitriumLimit); - - if (limitingFactor <= 0) - return ReactionResult.NoReaction; - - var reactionRate = Math.Min(limitingFactor, temperatureFactor); - - var hyperNobBurned = reactionRate * Atmospherics.ZaukerHyperNobRatio; - var nitriumBurned = reactionRate * Atmospherics.ZaukerNitriumRatio; - - var zaukerProduced = (hyperNobBurned + nitriumBurned) * Atmospherics.ZaukerProductionEfficiency; - - zaukerProduced = Math.Min(zaukerProduced, Atmospherics.ZaukerProductionMaxPerTick); - - var efficiencyRatio = zaukerProduced / ((hyperNobBurned + nitriumBurned) * Atmospherics.ZaukerProductionEfficiency); - hyperNobBurned *= efficiencyRatio; - nitriumBurned *= efficiencyRatio; - - mixture.AdjustMoles(Gas.HyperNoblium, -hyperNobBurned); - mixture.AdjustMoles(Gas.Nitrium, -nitriumBurned); - mixture.AdjustMoles(Gas.Zauker, zaukerProduced); - - var energyConsumed = zaukerProduced * Atmospherics.ZaukerProductionEnergy / heatScale; - var heatCapacity = atmosphereSystem.GetHeatCapacity(mixture, true); - if (heatCapacity > Atmospherics.MinimumHeatCapacity) - mixture.Temperature = Math.Max(mixture.Temperature - energyConsumed / heatCapacity, Atmospherics.TCMB); - - return ReactionResult.Reacting; - } -} diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index cada28798ca..e22f9df1444 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Atmos /// /// Class to store atmos constants. /// - public static class Atmospherics + public static partial class Atmospherics { #region ATMOS /// @@ -333,7 +333,6 @@ public static class Atmospherics public const float PluoxiumOxygenRatio = 0.5f; public const float PluoxiumTritiumRatio = 0.01f; public const float PluoxiumHydrogenByproductRatio = 0.01f; - public const float PluoxiumFormationEnergy = 1000f; /// Nitrium Production Constants public const float NitriumProductionMinTemp = 1500f; @@ -384,18 +383,7 @@ public static class Atmospherics public const float PNBZaseMaxTemp = 280f; public const float PNBZaseConversionRate = 0.2f; public const float PNBZaseEnergy = 60000f; - /// Proto-Nitrate Reactions end - - /// Nitrium Decomposition - public const float NitriumDecompositionMaxTemp = 343f; - public const float NitriumDecompositionTempDivisor = 100f; - public const float NitriumDecompositionRate = 1.2f; - public const float NitriumDecompositionEnergy = 30000f; - - /// Zauker Decomposition - public const float ZaukerDecompositionMaxRate = 20f; - public const float ZaukerDecompositionEnergy = 460f; - /// Adventure gases end + #endregion /// diff --git a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs index 115cb548929..2c07f9d509f 100644 --- a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs +++ b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs @@ -1,104 +1,103 @@ using Robust.Shared.GameStates; -using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.Atmos.Components; +/// +/// Used for gas analyzers, an item that shows players the gas contents of an atmos +/// device they use it on or of the tile they are standing on. +/// [RegisterComponent, NetworkedComponent] public sealed partial class GasAnalyzerComponent : Component { - [ViewVariables] + /// + /// The target entity currently being analyzed. + /// public EntityUid? Target; - [ViewVariables] - public EntityUid User; + /// + /// The current user of the gas analyzer. + /// + public EntityUid? User; - [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + /// + /// Is the analyzer currently active? + /// + [DataField] public bool Enabled; +} - [Serializable, NetSerializable] - public enum GasAnalyzerUiKey - { - Key, - } +/// +/// Atmospheric data is gathered in the system and sent to the user. +/// +[Serializable, NetSerializable] +public sealed class GasAnalyzerUserMessage(GasMixEntry[] nodeGasMixes, string deviceName, NetEntity deviceUid, bool deviceFlipped) : BoundUserInterfaceMessage +{ + public string DeviceName = deviceName; + public NetEntity DeviceUid = deviceUid; + public bool DeviceFlipped = deviceFlipped; + public GasMixEntry[] NodeGasMixes = nodeGasMixes; +} +/// +/// Contains information on a gas mix entry, turns into a tab in the UI. +/// +[Serializable, NetSerializable] +public readonly record struct GasMixEntry(string Name, float Volume, float Pressure, float Temperature, GasEntry[]? Gases = null) +{ /// - /// Atmospheric data is gathered in the system and sent to the user + /// Name of the tab in the UI. /// - [Serializable, NetSerializable] - public sealed class GasAnalyzerUserMessage : BoundUserInterfaceMessage - { - public string DeviceName; - public NetEntity DeviceUid; - public bool DeviceFlipped; - public string? Error; - public GasMixEntry[] NodeGasMixes; - public GasAnalyzerUserMessage(GasMixEntry[] nodeGasMixes, string deviceName, NetEntity deviceUid, bool deviceFlipped, string? error = null) - { - NodeGasMixes = nodeGasMixes; - DeviceName = deviceName; - DeviceUid = deviceUid; - DeviceFlipped = deviceFlipped; - Error = error; - } - } - + public readonly string Name = Name; /// - /// Contains information on a gas mix entry, turns into a tab in the UI + /// Volume of this gas mixture. /// - [Serializable, NetSerializable] - public struct GasMixEntry - { - /// - /// Name of the tab in the UI - /// - public readonly string Name; - public readonly float Volume; - public readonly float Pressure; - public readonly float Temperature; - public readonly GasEntry[]? Gases; - - public GasMixEntry(string name, float volume, float pressure, float temperature, GasEntry[]? gases = null) - { - Name = name; - Volume = volume; - Pressure = pressure; - Temperature = temperature; - Gases = gases; - } - } - + public readonly float Volume = Volume; /// - /// Individual gas entry data for populating the UI + /// Pressure of this gas mixture. /// - [Serializable, NetSerializable] - public struct GasEntry - { - public readonly string Name; - public readonly float Amount; - public readonly string Color; + public readonly float Pressure = Pressure; + /// + /// Temperature of this gas mixture. + /// + public readonly float Temperature = Temperature; + /// + /// The gases contained in this gas mixture. + /// The gases below a certain mol threshold are not included. + /// + public readonly GasEntry[]? Gases = Gases; +} - public GasEntry(string name, float amount, string color) - { - Name = name; - Amount = amount; - Color = color; - } +/// +/// Individual gas entry data for populating the UI. +/// +[Serializable, NetSerializable] +public readonly record struct GasEntry(Gas Gas, float Amount) +{ + /// + /// The gas this entry represents. + /// + public readonly Gas Gas = Gas; + /// + /// The gas amount in mol. + /// + public readonly float Amount = Amount; +} - public override string ToString() - { - // e.g. "Plasma: 2000 mol" - return Loc.GetString( - "gas-entry-info", - ("gasName", Name), - ("gasAmount", Amount)); - } - } +/// +/// Key for the GasAnalyzerBoundUserInterface. +/// +[Serializable, NetSerializable] +public enum GasAnalyzerUiKey +{ + Key, } +/// +/// Individual gas entry data for populating the UI +/// [Serializable, NetSerializable] public enum GasAnalyzerVisuals : byte { Enabled, } - diff --git a/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs index d64c8907afb..3c6e233604e 100644 --- a/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs +++ b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Atmos.Consoles; using Content.Shared.Atmos.Monitor; +using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -27,6 +28,20 @@ public sealed partial class AtmosAlertsComputerComponent : Component /// [ViewVariables, AutoNetworkedField] public HashSet SilencedDevices = new(); + + // Adventure-start + [DataField] + public SoundSpecifier? BeepSound; + + [DataField] + public TimeSpan Timer = TimeSpan.FromSeconds(5); + + [DataField] + public TimeSpan NextBeep = TimeSpan.Zero; + + [DataField, AutoNetworkedField] + public bool DoAtmosAlert = false; + // Adventure-end } [Serializable, NetSerializable] @@ -77,7 +92,7 @@ public struct AtmosAlertsFocusDeviceData public (float, AtmosAlarmType) PressureData; /// - /// Moles, percentage, and related alert state, for all detected gases + /// Moles, percentage, and related alert state, for all detected gases /// public Dictionary GasData; @@ -98,7 +113,7 @@ public AtmosAlertsFocusDeviceData } [Serializable, NetSerializable] -public sealed class AtmosAlertsComputerBoundInterfaceState : BoundUserInterfaceState +public sealed partial class AtmosAlertsComputerBoundInterfaceState : BoundUserInterfaceState // Adventure - edit добавлено partial { /// /// A list of all air alarms @@ -115,6 +130,7 @@ public sealed class AtmosAlertsComputerBoundInterfaceState : BoundUserInterfaceS /// public AtmosAlertsFocusDeviceData? FocusData; + /// /// Sends data from the server to the client to populate the atmos monitoring console UI /// @@ -200,7 +216,7 @@ public sealed class AtmosAlertsComputerDeviceSilencedMessage : BoundUserInterfac public bool SilenceDevice = true; /// - /// Used to inform the server that the client has silenced alerts from the specified device to this atmos monitoring console + /// Used to inform the server that the client has silenced alerts from the specified device to this atmos monitoring console /// public AtmosAlertsComputerDeviceSilencedMessage(NetEntity atmosDevice, bool silenceDevice = true) { @@ -209,6 +225,14 @@ public AtmosAlertsComputerDeviceSilencedMessage(NetEntity atmosDevice, bool sile } } +// Adventure-start +[Serializable, NetSerializable] +public sealed class AtmosAlertsComputerAlertSoundToggleMessage(bool enabled) : BoundUserInterfaceMessage +{ + public bool Enabled = enabled; +} +// Adventure-end + /// /// List of all the different atmos device groups /// diff --git a/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs b/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs index 7e2b2b04670..ed863755497 100644 --- a/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs +++ b/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs @@ -9,6 +9,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnDeviceSilencedMessage); + + InitializeADT(); } private void OnDeviceSilencedMessage(EntityUid uid, AtmosAlertsComputerComponent component, AtmosAlertsComputerDeviceSilencedMessage args) @@ -21,4 +23,5 @@ private void OnDeviceSilencedMessage(EntityUid uid, AtmosAlertsComputerComponent Dirty(uid, component); } + } diff --git a/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs new file mode 100644 index 00000000000..04b733fad66 --- /dev/null +++ b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.Gases.cs @@ -0,0 +1,69 @@ +using System; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; +using Robust.Shared.Maths; + +namespace Content.Shared.Atmos.EntitySystems; + +public abstract partial class SharedAtmosphereSystem +{ + protected float[] GasFuelMask = new float[Atmospherics.AdjustedNumberOfGases]; + protected float[] GasOxidizerMask = new float[Atmospherics.AdjustedNumberOfGases]; + protected float[] GasSpecificHeats = new float[Atmospherics.AdjustedNumberOfGases]; + + protected virtual void InitializeGases() + { + var simdLen = MathHelper.NextMultipleOf(Atmospherics.TotalNumberOfGases, 4); + Array.Resize(ref GasFuelMask, simdLen); + Array.Resize(ref GasOxidizerMask, simdLen); + Array.Resize(ref GasSpecificHeats, simdLen); + + for (var i = 0; i < GasPrototypes.Length; i++) + { + var proto = GasPrototypes[i]; + GasFuelMask[i] = proto.IsFuel ? 1f : 0f; + GasOxidizerMask[i] = proto.IsOxidizer ? 1f : 0f; + GasSpecificHeats[i] = proto.MolarHeatCapacity; + } + } + + public virtual bool IsMixtureFuel(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) + => throw new NotImplementedException(); + + public virtual bool IsMixtureOxidizer(GasMixture mixture, float epsilon = Atmospherics.GasMinMoles) + => throw new NotImplementedException(); + + protected virtual float GetHeatCapacityCalculation(float[] moles, bool space) + => throw new NotImplementedException(); + + public virtual ReactionResult React(GasMixture mixture, IGasMixtureHolder? holder) + => ReactionResult.NoReaction; + + // ---- Test-visible aliases / shared heat-capacity API ---- // + + /// + /// Public read accessor for the SIMD-padded per-gas specific heats array. + /// + public float[] GasMolarHeatCapacities => GasSpecificHeats; + + /// + /// Speedup scale for heat-capacity calculations, populated from + /// CCVars.AtmosHeatScale in server/client CVars files. + /// + public float HeatScale { get; protected set; } = 1.0f; + + /// + /// Returns the heat capacity of a gas mixture. + /// + /// The gas mixture to evaluate. + /// + /// false (default) — returns the raw unscaled value.
+ /// true — divides by + /// (the atmospheric simulation speedup factor). + /// + public float GetHeatCapacity(GasMixture mixture, bool applyScaling = false) + { + var raw = GetHeatCapacityCalculation(mixture.Moles, mixture.Immutable); + return applyScaling && HeatScale > 0f ? raw / HeatScale : raw; + } +} diff --git a/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.cs b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.cs index 67d6dec8af5..72c4ef7f2b7 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedAtmosphereSystem.cs @@ -1,6 +1,10 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Atmos; using Content.Shared.Atmos.Prototypes; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; +using Robust.Shared.Log; using Robust.Shared.Prototypes; namespace Content.Shared.Atmos.EntitySystems @@ -13,7 +17,6 @@ public abstract partial class SharedAtmosphereSystem : EntitySystem private EntityQuery _internalsQuery; public string?[] GasReagents = new string[Atmospherics.TotalNumberOfGases]; - protected readonly GasPrototype[] GasPrototypes = new GasPrototype[Atmospherics.TotalNumberOfGases]; public override void Initialize() @@ -26,13 +29,14 @@ public override void Initialize() foreach (var gas in Enum.GetValues()) { - var idx = (int)gas; - // Log an error if the corresponding prototype isn't found + var idx = (int) gas; + if (!_prototypeManager.TryIndex(gas.ToString(), out var gasPrototype)) { - Log.Error($"Failed to find corresponding {nameof(GasPrototype)} for gas ID {(int)gas} ({gas}) with expected ID \"{gas.ToString()}\". Is your prototype named correctly?"); + Log.Error($"Failed to find corresponding {nameof(GasPrototype)} for gas ID {(int) gas} ({gas}) with expected ID \"{gas}\". Is your prototype named correctly?"); continue; } + GasPrototypes[idx] = gasPrototype; GasReagents[idx] = gasPrototype.Reagent; } diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index d831616355b..e3250d3a24c 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -1,114 +1,249 @@ using Content.Shared.Atmos.Components; -using Content.Shared.Atmos.Prototypes; +using Robust.Shared.Configuration; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -namespace Content.Shared.Atmos.EntitySystems +namespace Content.Shared.Atmos.EntitySystems; + +public abstract class SharedGasTileOverlaySystem : EntitySystem { - public abstract class SharedGasTileOverlaySystem : EntitySystem + public const byte ChunkSize = 8; + protected float AccumulatedFrameTime; + protected bool PvsEnabled; + + [Dependency] protected readonly IPrototypeManager ProtoMan = default!; + [Dependency] protected readonly IConfigurationManager ConfMan = default!; + [Dependency] private readonly SharedAtmosphereSystem _atmosphere = default!; + + /// + /// array of the ids of all visible gases. + /// + public int[] VisibleGasId = default!; + + public override void Initialize() { - public const byte ChunkSize = 8; - protected float AccumulatedFrameTime; - protected bool PvsEnabled; + base.Initialize(); + SubscribeLocalEvent(OnGetState); - [Dependency] protected readonly IPrototypeManager ProtoMan = default!; - [Dependency] private readonly SharedAtmosphereSystem _atmosphere = default!; + List visibleGases = new(); - /// - /// array of the ids of all visible gases. - /// - public int[] VisibleGasId = default!; + for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) + { + var gasPrototype = _atmosphere.GetGas(i); + if (gasPrototype.GasOverlaySprite != null) + visibleGases.Add(i); + } + VisibleGasId = visibleGases.ToArray(); + } - public override void Initialize() + private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref ComponentGetState args) + { + if (PvsEnabled && !args.ReplayState) + return; + + // Should this be a full component state or a delta-state? + if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick) { - base.Initialize(); - SubscribeLocalEvent(OnGetState); + args.State = new GasTileOverlayState(component.Chunks); + return; + } - List visibleGases = new(); + args.State = new GasTileOverlayState(component.Chunks); + } - for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) - { - var gasPrototype = _atmosphere.GetGas(i); - if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture) || !string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) - visibleGases.Add(i); - } + public static Vector2i GetGasChunkIndices(Vector2i indices) + { + return new Vector2i((int)MathF.Floor((float)indices.X / ChunkSize), (int)MathF.Floor((float)indices.Y / ChunkSize)); + } + + [Serializable, NetSerializable] + public readonly struct GasOverlayData : IEquatable + { + [ViewVariables] public readonly byte FireState; + [ViewVariables] public readonly byte[] Opacity; + // TODO change fire color based on ByteTemp - VisibleGasId = visibleGases.ToArray(); + /// + /// Network-synced air temperature, compressed to a single byte per tile for bandwidth optimization. + /// Note: Values are approximate and may deviate even ~10°C from the precise server side only temperature. + /// + [ViewVariables] + public readonly ThermalByte ByteGasTemperature; + + + public GasOverlayData(byte fireState, byte[] opacity, ThermalByte byteTemp) + { + FireState = fireState; + Opacity = opacity; + ByteGasTemperature = byteTemp; } - private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref ComponentGetState args) + public bool Equals(GasOverlayData other) { - if (PvsEnabled && !args.ReplayState) - return; + if (FireState != other.FireState) + return false; - // Should this be a full component state or a delta-state? - if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick) - { - args.State = new GasTileOverlayState(component.Chunks); - return; - } + if (Opacity?.Length != other.Opacity?.Length) + return false; - var data = new Dictionary(); - foreach (var (index, chunk) in component.Chunks) + if (Opacity != null && other.Opacity != null) { - if (chunk.LastUpdate >= args.FromTick) - data[index] = chunk; + for (var i = 0; i < Opacity.Length; i++) + { + if (Opacity[i] != other.Opacity[i]) + return false; + } } - args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); - } + if (ByteGasTemperature != other.ByteGasTemperature) + return false; - public static Vector2i GetGasChunkIndices(Vector2i indices) - { - return new((int) MathF.Floor((float) indices.X / ChunkSize), (int) MathF.Floor((float) indices.Y / ChunkSize)); + return true; } + } - [Serializable, NetSerializable] - public readonly struct GasOverlayData : IEquatable - { - [ViewVariables] - public readonly byte FireState; + [Serializable, NetSerializable] + public sealed class GasOverlayUpdateEvent : EntityEventArgs + { + public Dictionary> UpdatedChunks = new(); + public Dictionary> RemovedChunks = new(); + } +} - [ViewVariables] - public readonly byte[] Opacity; +/// +/// Struct for networking gas temperatures to all clients using a single struct(byte) per tile. +/// +/// +/// +/// This struct compresses the gas temperature into a 1-byte value (0-255). +/// It clamps the temperature to a maximum of 1000K and divides it by 4, creating a range of 0-250. +/// This provides a resolution of 4 degrees Kelvin. +/// +/// +/// The remaining bytes are used as special flags: +/// +/// 255: Represents a Wall (block cannot hold atmosphere). +/// 254: Represents a Vacuum. +/// 251-253: Reserved for future use. +/// +/// +/// +/// Dirtying Logic: The value is only dirtied and networked if the difference between the +/// networked byte and the real atmosphere byte is greater than 1. This prevents network spam +/// from minor temperature fluctuations (e.g., heating from 1K to 8K will not trigger an update, +/// but hitting 9K moves the byte index enough to sync). +/// +/// +/// Currently, the conversion is linear. Future improvements might involve a quadratic scale +/// or pre-defined resolution points to offer higher precision at room temperatures +/// and lower precision at extreme temperatures (1000K). +/// +/// +[Serializable, NetSerializable] +public struct ThermalByte : IEquatable +{ + public const float TempMinimum = 0f; + public const float TempMaximum = 1000f; + public const int TempResolution = 250; - // TODO change fire color based on temps - // But also: dont dirty on a 0.01 kelvin change in temperatures. - // Either have a temp tolerance, or map temperature -> byte levels + public const byte ReservedFuture0 = 251; + public const byte ReservedFuture1 = 252; + public const byte ReservedFuture2 = 253; + public const byte StateVacuum = 254; + public const byte AtmosImpossible = 255; - public GasOverlayData(byte fireState, byte[] opacity) - { - FireState = fireState; - Opacity = opacity; - } + public const float TempDegreeResolution = (TempMaximum - TempMinimum) / TempResolution; + public const float TempToByteFactor = TempResolution / (TempMaximum - TempMinimum); - public bool Equals(GasOverlayData other) - { - if (FireState != other.FireState) - return false; + private byte _coreValue; - if (Opacity?.Length != other.Opacity?.Length) - return false; + public ThermalByte(float temperatureKelvin) + { + SetTemperature(temperatureKelvin); + } - if (Opacity != null && other.Opacity != null) - { - for (var i = 0; i < Opacity.Length; i++) - { - if (Opacity[i] != other.Opacity[i]) - return false; - } - } + public ThermalByte() + { + _coreValue = AtmosImpossible; + } - return true; - } - } + /// + /// Set temperature of air in this in Kelvin. + /// + public void SetTemperature(float temperatureKelvin) + { + var clampedTemp = Math.Clamp(temperatureKelvin, TempMinimum, TempMaximum); + _coreValue = (byte)((clampedTemp - TempMinimum) * TempResolution / (TempMaximum - TempMinimum)); + } + + public void SetAtmosIsImpossible() + { + _coreValue = AtmosImpossible; + } + + public void SetVacuum() + { + _coreValue = StateVacuum; + } - [Serializable, NetSerializable] - public sealed class GasOverlayUpdateEvent : EntityEventArgs + public bool IsAtmosImpossible => _coreValue == AtmosImpossible; // Cold space, solid walls + public bool IsVacuum => _coreValue == StateVacuum; + public byte Value => _coreValue; + + /// + /// Attempts to get the air temperature in Kelvin. + /// + /// The temperature in Kelvin, if the tile has a valid temperature. + /// + /// If true and the tile is a vacuum, will be set to + /// and the method will return . + /// + /// + /// if the tile contains a valid temperature (including vacuum if is set); + /// otherwise (e.g., walls). + /// + public readonly bool TryGetTemperature(out float temperature, bool onVacuumReturnTcmb = true) + { + switch (_coreValue) { - public Dictionary> UpdatedChunks = new(); - public Dictionary> RemovedChunks = new(); + case AtmosImpossible: + temperature = 0f; + return false; + case StateVacuum when onVacuumReturnTcmb: + temperature = Atmospherics.TCMB; + return true; + case StateVacuum: + temperature = 0f; + return false; + default: + temperature = (_coreValue * TempDegreeResolution) + TempMinimum; + return true; } } + + public bool Equals(ThermalByte other) + { + return _coreValue == other._coreValue; + } + + public static bool operator ==(ThermalByte left, ThermalByte right) + { + return left.Equals(right); + } + + public static bool operator !=(ThermalByte left, ThermalByte right) + { + return !left.Equals(right); + } + + public override bool Equals(object? obj) + { + return obj is ThermalByte other && Equals(other); + } + + public override int GetHashCode() + { + return _coreValue.GetHashCode(); + } } diff --git a/Content.Shared/Atmos/GasMixture.cs b/Content.Shared/Atmos/GasMixture.cs index 3da7827cdd8..a25f93b4cd4 100644 --- a/Content.Shared/Atmos/GasMixture.cs +++ b/Content.Shared/Atmos/GasMixture.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; diff --git a/Content.Shared/Atmos/GasMixtureStringRepresentation.cs b/Content.Shared/Atmos/GasMixtureStringRepresentation.cs index 942b2bdc672..33b9d9e181c 100644 --- a/Content.Shared/Atmos/GasMixtureStringRepresentation.cs +++ b/Content.Shared/Atmos/GasMixtureStringRepresentation.cs @@ -1,10 +1,19 @@ -namespace Content.Shared.Atmos; +using System.Text; + +namespace Content.Shared.Atmos; public readonly record struct GasMixtureStringRepresentation(float TotalMoles, float Temperature, float Pressure, Dictionary MolesPerGas) : IFormattable { public override string ToString() { - return $"{Temperature}K {Pressure} kPa"; + var stringBuilder = new StringBuilder(); + foreach (var (gas, moles) in MolesPerGas) + { + stringBuilder.Append($"{gas}: {moles}, "); + } + var result = stringBuilder.ToString(); + + return $"{Temperature} K, {Pressure} kPa, {result}Total Moles: {TotalMoles}"; } public string ToString(string? format, IFormatProvider? formatProvider) diff --git a/Content.Shared/Atmos/Piping/Unary/Components/SharedVentScrubberComponent.cs b/Content.Shared/Atmos/Piping/Unary/Components/SharedVentScrubberComponent.cs index d77ff42ebcf..371b37f733b 100644 --- a/Content.Shared/Atmos/Piping/Unary/Components/SharedVentScrubberComponent.cs +++ b/Content.Shared/Atmos/Piping/Unary/Components/SharedVentScrubberComponent.cs @@ -9,14 +9,14 @@ public sealed class GasVentScrubberData : IAtmosDeviceData public bool Enabled { get; set; } public bool Dirty { get; set; } public bool IgnoreAlarms { get; set; } = false; - public HashSet FilterGases { get; set; } = new(DefaultFilterGases); + public HashSet FilterGases { get; set; } = new(_defaultFilterGases); public ScrubberPumpDirection PumpDirection { get; set; } = ScrubberPumpDirection.Scrubbing; public float VolumeRate { get; set; } = 200f; public bool WideNet { get; set; } = false; public bool AirAlarmPanicWireCut { get; set; } - public static HashSet DefaultFilterGases = new() - { + public static HashSet _defaultFilterGases = + [ Gas.CarbonDioxide, Gas.Plasma, Gas.Tritium, @@ -24,27 +24,25 @@ public sealed class GasVentScrubberData : IAtmosDeviceData Gas.Ammonia, Gas.NitrousOxide, Gas.Frezon, - // Adventure gases begin Gas.BZ, - Gas.Halon, - Gas.Healium, - Gas.HyperNoblium, - Gas.Hydrogen, Gas.Pluoxium, + Gas.Hydrogen, Gas.Nitrium, + Gas.Healium, + Gas.HyperNoblium, + Gas.ProtoNitrate, + Gas.Zauker, + Gas.Halon, Gas.Helium, Gas.AntiNoblium, - Gas.ProtoNitrate, - Gas.Zauker - // Adventure gases end - }; + ]; // Presets for 'dumb' air alarm modes public static GasVentScrubberData FilterModePreset = new GasVentScrubberData { Enabled = true, - FilterGases = new(GasVentScrubberData.DefaultFilterGases), + FilterGases = new(GasVentScrubberData._defaultFilterGases), PumpDirection = ScrubberPumpDirection.Scrubbing, VolumeRate = 200f, WideNet = false @@ -53,7 +51,7 @@ public sealed class GasVentScrubberData : IAtmosDeviceData public static GasVentScrubberData WideFilterModePreset = new GasVentScrubberData { Enabled = true, - FilterGases = new(GasVentScrubberData.DefaultFilterGases), + FilterGases = new(GasVentScrubberData._defaultFilterGases), PumpDirection = ScrubberPumpDirection.Scrubbing, VolumeRate = 200f, WideNet = true @@ -63,7 +61,7 @@ public sealed class GasVentScrubberData : IAtmosDeviceData { Enabled = false, Dirty = true, - FilterGases = new(GasVentScrubberData.DefaultFilterGases), + FilterGases = new(GasVentScrubberData._defaultFilterGases), PumpDirection = ScrubberPumpDirection.Scrubbing, VolumeRate = 200f, WideNet = false @@ -73,7 +71,7 @@ public sealed class GasVentScrubberData : IAtmosDeviceData { Enabled = true, Dirty = true, - FilterGases = new(GasVentScrubberData.DefaultFilterGases), + FilterGases = new(GasVentScrubberData._defaultFilterGases), PumpDirection = ScrubberPumpDirection.Siphoning, VolumeRate = 200f, WideNet = true @@ -84,7 +82,7 @@ public sealed class GasVentScrubberData : IAtmosDeviceData Enabled = true, IgnoreAlarms = true, Dirty = true, - FilterGases = new(GasVentScrubberData.DefaultFilterGases), + FilterGases = new(GasVentScrubberData._defaultFilterGases), PumpDirection = ScrubberPumpDirection.Siphoning, VolumeRate = 200f, WideNet = false diff --git a/Content.Shared/Atmos/Prototypes/GasPrototype.cs b/Content.Shared/Atmos/Prototypes/GasPrototype.cs index 6a2d4078207..524f8a59c45 100644 --- a/Content.Shared/Atmos/Prototypes/GasPrototype.cs +++ b/Content.Shared/Atmos/Prototypes/GasPrototype.cs @@ -1,87 +1,133 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Reagent; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Atmos.Prototypes +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Atmos.Prototypes; + +/// +/// Prototype defining a gas for atmospherics. +/// +/// +/// The total number of gases is hardcoded in a bunch of places. +/// If you add any new ones, make sure to also adjust the constants in accordingly. +/// +[Prototype] +public sealed partial class GasPrototype : IPrototype, ISerializationHooks { - [Prototype] - public sealed partial class GasPrototype : IPrototype + // TODO: Add interfaces for gas behaviours e.g. breathing, burning + + /// + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The name of the gas as shown to the player. + /// + [DataField(required: true)] + public LocId Name; + + /// + /// The abbreviation of the name. For example O₂ for Oxygen. + /// Used for UI purposes. + /// + [DataField] + public LocId? Abbreviation; + + /// + /// The molar heat capacity of this gas, in J/(K * mol). + /// Describes how much heat energy is needed to heat up this gas by one Kelvin. + /// Or in other words, the higher this number is the more energy this gas can store. + /// + /// + /// This will be divided by the cvar. + /// + [DataField] + public float MolarHeatCapacity; + + /// + /// Heat capacity ratio for gas. + /// TODO: Make gas pumps do proper adiabatic compression so that this is actually used. + /// + [DataField] + public float HeatCapacityRatio = 1.4f; + + /// + /// Molar mass of the gas. + /// TODO: This is not used anywhere, do we even need this? + /// + [DataField] + public float MolarMass = 1f; + + + /// + /// Minimum amount of moles for this gas to be visible. + /// + [DataField] + public float GasMolesVisible = 0.25f; + + /// + /// Visibility for this gas will be max after this value. + /// + [ViewVariables] + public float GasMolesVisibleMax => GasMolesVisible * GasVisibilityFactor; + + /// + /// Multiplier that decides when a gas will be at maximum visibility. + /// + [DataField] + public float GasVisibilityFactor = Atmospherics.FactorGasVisibleMax; + + /// + /// Sprite to show in the gas overlay if this gas is present on a tile. + /// If null the gas will be invisible. + /// + [DataField] + public SpriteSpecifier? GasOverlaySprite; + + /// + /// The reagent that this gas will turn into when inhaled or condensed. + /// + [DataField] + public ProtoId? Reagent; + + /// + /// The color of the gas used for UI purposes. + /// + [DataField] + public Color Color = Color.White; + + /// + /// The price per mole when this gas is sold at cargo. + /// The final price will also depend on the purity of the gas mixture. + /// + [DataField] + public float PricePerMole = 0; + + /// + /// Whether the gas is considered to be flammable. + /// This is used generically across Atmospherics to determine + /// if things like hotspots are allowed to ignite if an + /// oxidizer is present. + /// + [DataField] + public bool IsFuel; + + /// + /// Whether the gas is considered to be an oxidizer. + /// Same reasoning as but for oxidizers. + /// + [DataField] + public bool IsOxidizer; + + [DataField("gasOverlayTexture")] public string? GasOverlayTexture; + [DataField("gasOverlayState")] public string? GasOverlayState; + + void ISerializationHooks.AfterDeserialization() { - [DataField("name")] public string Name { get; set; } = ""; - - // TODO: Control gas amount necessary for overlay to appear - // TODO: Add interfaces for gas behaviours e.g. breathing, burning - - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// Specific heat for gas. - /// - [DataField("specificHeat")] - public float SpecificHeat { get; private set; } - - /// - /// Heat capacity ratio for gas - /// - [DataField("heatCapacityRatio")] - public float HeatCapacityRatio { get; private set; } = 1.4f; - - /// - /// Molar mass of gas - /// - [DataField("molarMass")] - public float MolarMass { get; set; } = 1f; - - - /// - /// Minimum amount of moles for this gas to be visible. - /// - [DataField("gasMolesVisible")] - public float GasMolesVisible { get; private set; } = 0.25f; - - /// - /// Visibility for this gas will be max after this value. - /// - public float GasMolesVisibleMax => GasMolesVisible * GasVisibilityFactor; - - [DataField("gasVisbilityFactor")] - public float GasVisibilityFactor = Atmospherics.FactorGasVisibleMax; - - /// - /// If this reagent is in gas form, this is the path to the overlay that will be used to make the gas visible. - /// - [DataField("gasOverlayTexture")] - public string GasOverlayTexture { get; private set; } = string.Empty; - - /// - /// If this reagent is in gas form, this will be the path to the RSI sprite that will be used to make the gas visible. - /// - [DataField("gasOverlayState")] - public string GasOverlayState { get; set; } = string.Empty; - - /// - /// State for the gas RSI overlay. - /// - [DataField("gasOverlaySprite")] - public string GasOverlaySprite { get; set; } = string.Empty; - - /// - /// Path to the tile overlay used when this gas appears visible. - /// - [DataField("overlayPath")] - public string OverlayPath { get; private set; } = string.Empty; - - /// - /// The reagent that this gas will turn into when inhaled. - /// - [DataField("reagent", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? Reagent { get; private set; } = default!; - - [DataField("color")] public string Color { get; private set; } = string.Empty; - - [DataField("pricePerMole")] - public float PricePerMole { get; set; } = 0; + if (GasOverlayTexture != null && GasOverlayState != null) + GasOverlaySprite = new SpriteSpecifier.Rsi(new ResPath(GasOverlayTexture), GasOverlayState); } + } diff --git a/Content.Shared/CCVar/CCVars.Atmos.cs b/Content.Shared/CCVar/CCVars.Atmos.cs index 7ef40b7911b..2516d6a908d 100644 --- a/Content.Shared/CCVar/CCVars.Atmos.cs +++ b/Content.Shared/CCVar/CCVars.Atmos.cs @@ -8,7 +8,7 @@ public sealed partial class CCVars /// Whether gas differences will move entities. ///
public static readonly CVarDef SpaceWind = - CVarDef.Create("atmos.space_wind", false, CVar.SERVERONLY); + CVarDef.Create("atmos.space_wind", true, CVar.SERVERONLY); /// /// Divisor from maxForce (pressureDifference * 2.25f) to force applied on objects. @@ -57,7 +57,7 @@ public sealed partial class CCVars /// Also looks weird on slow spacing for unrelated reasons. If you do want to enable this, you should probably turn on instaspacing. /// public static readonly CVarDef MonstermosRipTiles = - CVarDef.Create("atmos.monstermos_rip_tiles", false, CVar.SERVERONLY); + CVarDef.Create("atmos.monstermos_rip_tiles", true, CVar.SERVERONLY); /// /// Whether explosive depressurization will cause the grid to gain an impulse. @@ -120,7 +120,7 @@ public sealed partial class CCVars /// Maximum time in milliseconds that atmos can take processing. /// public static readonly CVarDef AtmosMaxProcessTime = - CVarDef.Create("atmos.max_process_time", 3f, CVar.SERVERONLY); + CVarDef.Create("atmos.max_process_time", 5f, CVar.SERVERONLY); /// /// Atmos tickrate in TPS. Atmos processing will happen every 1/TPS seconds. @@ -142,7 +142,7 @@ public sealed partial class CCVars /// gases heat up and cool down 64x faster than real life. /// public static readonly CVarDef AtmosHeatScale = - CVarDef.Create("atmos.heat_scale", 8f, CVar.SERVERONLY); + CVarDef.Create("atmos.heat_scale", 1.0f, CVar.SERVER | CVar.REPLICATED); /// /// Maximum explosion radius for explosions caused by bursting a gas tank ("max caps"). @@ -177,4 +177,7 @@ public sealed partial class CCVars /// public static readonly CVarDef DeltaPressureParallelBatchSize = CVarDef.Create("atmos.delta_pressure_parallel_batch_size", 10, CVar.SERVERONLY); + + public static readonly CVarDef GasOverlayThermalDirtyThreshold = + CVarDef.Create("atmos.overlay_thermal_dirty_threshold", 1, CVar.SERVERONLY); } diff --git a/Content.Shared/DrawDepth/DrawDepth.cs b/Content.Shared/DrawDepth/DrawDepth.cs index 35fead5b3fd..288602353c8 100644 --- a/Content.Shared/DrawDepth/DrawDepth.cs +++ b/Content.Shared/DrawDepth/DrawDepth.cs @@ -112,17 +112,22 @@ public enum DrawDepth ///
Overdoors = DrawDepthTag.Default + 10, + /// + /// Visible atmos gas. + /// + Gasses = DrawDepthTag.Default + 11, + /// /// Explosions, fire, melee swings. Whatever. /// - Effects = DrawDepthTag.Default + 11, + Effects = DrawDepthTag.Default + 12, - Ghosts = DrawDepthTag.Default + 12, + Ghosts = DrawDepthTag.Default + 13, /// /// Use this selectively if it absolutely needs to be drawn above (almost) everything else. Examples include /// the pointing arrow, the drag & drop ghost-entity, and some debug tools. /// - Overlays = DrawDepthTag.Default + 13, + Overlays = DrawDepthTag.Default + 14, } } diff --git a/Content.Shared/_Adventure/ADTCCVars/ADTCCVars.Atmos.cs b/Content.Shared/_Adventure/ADTCCVars/ADTCCVars.Atmos.cs new file mode 100644 index 00000000000..d1a1372cbcd --- /dev/null +++ b/Content.Shared/_Adventure/ADTCCVars/ADTCCVars.Atmos.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Configuration; + +namespace Content.Shared._Adventure.ADTCCVars; + +[CVarDefs] +public sealed class ADTCCVars +{ + /// + /// These variables control modifications of various gas prices. If gas has no specified + /// modifier here, it will use default price from prototype + /// + + public static readonly CVarDef DefaultGasPriceModifier = + CVarDef.Create("atmos.gas_price_modifier_default", 1f, CVar.SERVER); + + public static readonly CVarDef GasPriceModifierTritium = + CVarDef.Create("atmos.gas_price_modifier_tritium", 2.5f, CVar.SERVER); + + public static readonly CVarDef GasPriceModifierNitrousOxide = + CVarDef.Create("atmos.gas_price_modifier_nitrous_oxide", 0.1f, CVar.SERVER); + + public static readonly CVarDef GasPriceModifierFrezon = + CVarDef.Create("atmos.gas_price_modifier_frezon", 1f, CVar.SERVER); +} diff --git a/Content.Shared/_Adventure/Atmos/Atmospherics.cs b/Content.Shared/_Adventure/Atmos/Atmospherics.cs new file mode 100644 index 00000000000..4dd12843d79 --- /dev/null +++ b/Content.Shared/_Adventure/Atmos/Atmospherics.cs @@ -0,0 +1,44 @@ +namespace Content.Shared.Atmos; + +public static partial class Atmospherics +{ + /// + /// Defines energy released in N2O decomposition reaction. + /// + public const float NitrousOxideDecompositionEnergy = 200000f; + + /// + /// Defines energy released in Pluoxium formation. + /// + public const float PluoxiumFormationEnergy = 250f; + + /// + /// The maximum amount of pluoxium that can form per reaction tick. + /// + public const float PluoxiumMaxRate = 5f; + public const float FireH2EnergyReleased = 2800000f; + public const float H2OxygenFullBurn = 10f; + public const float FireH2BurnRateDelta = 2f; + public const float H2MinimumBurnTemperature = T0C + 100f; + public const float NitriumFormationTempDivisor = (T0C + 100f) * 8f; + public const float NitriumFormationEnergy = 100000f; + public const float NitriumDecompositionTempDivisor = (T0C + 100f) * 8f; + public const float NitriumDecompositionEnergy = 30000f; + public const float NitriumDecompositionMaxTemp = T0C + 70f; + public const float NobliumFormationEnergy = 20000000f; + public const float ReactionOpperssionThreshold = 5f; + public const float HalonFormationEnergy = 300f; + public const float HalonCombustionEnergy = 2500f; + public const float HealiumFormationEnergy = 9000f; + public const float ZaukerFormationEnergy = 5000f; + public const float ZaukerFormationTemperatureScale = 0.000005f; + public const float ZaukerDecompositionMaxRate = 20f; + public const float ZaukerDecompositionEnergy = 460f; + public const float ProtoNitrateTemperatureScale = 0.005f; + public const float ProtoNitrateFormationEnergy = 650f; + public const float ProtoNitrateHydrogenConversionThreshold = 150f; + public const float ProtoNitrateHydrogenConversionMaxRate = 5f; + public const float ProtoNitrateHydrogenConversionEnergy = 2500f; + public const float ProtoNitrateTritiumConversionEnergy = 10000f; + public const float ProtoNitrateBZaseConversionEnergy = 60000f; +} diff --git a/Content.Shared/_Adventure/Atmos/Consoles/Components/AtmosAlertsComputerComponent.Sunrise.cs b/Content.Shared/_Adventure/Atmos/Consoles/Components/AtmosAlertsComputerComponent.Sunrise.cs new file mode 100644 index 00000000000..ca140512491 --- /dev/null +++ b/Content.Shared/_Adventure/Atmos/Consoles/Components/AtmosAlertsComputerComponent.Sunrise.cs @@ -0,0 +1,19 @@ +namespace Content.Shared.Atmos.Components; + +public sealed partial class AtmosAlertsComputerBoundInterfaceState +{ + /// + /// Управление пищалкой + /// + public bool DoAtmosAlert; + + public AtmosAlertsComputerBoundInterfaceState( + AtmosAlertsComputerEntry[] airAlarms, + AtmosAlertsComputerEntry[] fireAlarms, + AtmosAlertsFocusDeviceData? focusData, + bool doAtmosAlert) + : this(airAlarms, fireAlarms, focusData) + { + DoAtmosAlert = doAtmosAlert; + } +} diff --git a/Content.Shared/_Adventure/Atmos/Consoles/SharedAtmosAlertsComputerSystem.Sunrise.cs b/Content.Shared/_Adventure/Atmos/Consoles/SharedAtmosAlertsComputerSystem.Sunrise.cs new file mode 100644 index 00000000000..3e3ef5bef4e --- /dev/null +++ b/Content.Shared/_Adventure/Atmos/Consoles/SharedAtmosAlertsComputerSystem.Sunrise.cs @@ -0,0 +1,41 @@ +using Content.Shared.Atmos.Components; +using Content.Shared.Verbs; + +namespace Content.Shared.Atmos.Consoles; + +public abstract partial class SharedAtmosAlertsComputerSystem +{ + private void InitializeADT() + { + SubscribeLocalEvent(OnAlertSoundToggleMessage); + SubscribeLocalEvent>(AddToggleVerb); + } + + private void OnAlertSoundToggleMessage(Entity ent, ref AtmosAlertsComputerAlertSoundToggleMessage args) + { + ent.Comp.DoAtmosAlert = args.Enabled; + Dirty(ent); + } + + private void AddToggleVerb(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var verb = new InteractionVerb + { + Text = Loc.GetString(ent.Comp.DoAtmosAlert + ? "item-toggle-deactivate-alert" + : "item-toggle-activate-alert"), + Act = () => ToggleAlert(ent), + }; + + args.Verbs.Add(verb); + } + + private void ToggleAlert(Entity ent) + { + ent.Comp.DoAtmosAlert = !ent.Comp.DoAtmosAlert; + Dirty(ent); + } +} diff --git a/Resources/Prototypes/Atmospherics/gases.yml b/Resources/Prototypes/Atmospherics/gases.yml index e12bd0225df..a8ce3bd66ea 100644 --- a/Resources/Prototypes/Atmospherics/gases.yml +++ b/Resources/Prototypes/Atmospherics/gases.yml @@ -1,102 +1,114 @@ - type: gas id: Oxygen - name: gases-oxygen - specificHeat: 20 + name: gas-oxygen + abbreviation: gas-oxygen-abbreviation + molarHeatCapacity: 20 heatCapacityRatio: 1.4 molarMass: 32 - color: 2887E8 + color: '#2887E8' reagent: Oxygen pricePerMole: 0 + isOxidizer: true - type: gas id: Nitrogen - name: gases-nitrogen - specificHeat: 30 + name: gas-nitrogen + abbreviation: gas-nitrogen-abbreviation + molarHeatCapacity: 30 heatCapacityRatio: 1.4 molarMass: 28 - color: DA1010 + color: '#DA1010' reagent: Nitrogen pricePerMole: 0 - type: gas id: CarbonDioxide - name: gases-co2 - specificHeat: 30 + name: gas-carbon-dioxide + abbreviation: gas-carbon-dioxide-abbreviation + molarHeatCapacity: 30 heatCapacityRatio: 1.3 molarMass: 44 - color: 4e4e4e + color: '#4e4e4e' reagent: CarbonDioxide pricePerMole: 0 - type: gas id: Plasma - name: gases-plasma - specificHeat: 200 + name: gas-plasma + abbreviation: gas-plasma-abbreviation + molarHeatCapacity: 200 heatCapacityRatio: 1.7 molarMass: 120 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi + gasOverlayTexture: /Textures/Effects/atmospherics.rsi gasOverlayState: plasma - color: FF3300 + color: '#FF3300' reagent: Plasma pricePerMole: 0 + isFuel: true - type: gas id: Tritium - name: gases-tritium - specificHeat: 10 + name: gas-tritium + abbreviation: gas-tritium-abbreviation + molarHeatCapacity: 10 heatCapacityRatio: 1.3 molarMass: 6 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi + gasOverlayTexture: /Textures/Effects/atmospherics.rsi gasOverlayState: tritium - color: 13FF4B + color: '#13FF4B' reagent: Tritium - pricePerMole: 0.1 # Adventure gases + pricePerMole: 2.5 + isFuel: true - type: gas id: WaterVapor - name: gases-water-vapor - specificHeat: 40 + name: gas-water-vapor + abbreviation: gas-water-vapor-abbreviation + molarHeatCapacity: 40 heatCapacityRatio: 1.33 molarMass: 18 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi + gasOverlayTexture: /Textures/Effects/atmospherics.rsi gasOverlayState: water_vapor - color: bffffd + color: '#bffffd' reagent: Water pricePerMole: 0 - type: gas id: Ammonia - name: gases-ammonia - specificHeat: 20 + name: gas-ammonia + abbreviation: gas-ammonia-abbreviation + molarHeatCapacity: 20 heatCapacityRatio: 1.4 molarMass: 44 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi + gasOverlayTexture: /Textures/Effects/atmospherics.rsi gasOverlayState: miasma gasMolesVisible: 2 - gasVisbilityFactor: 3.5 - color: 56941E + gasVisibilityFactor: 3.5 + color: '#56941E' reagent: Ammonia pricePerMole: 0.15 - type: gas id: NitrousOxide - name: gases-n2o - specificHeat: 40 + name: gas-nitrous-oxide + abbreviation: gas-nitrous-oxide-abbreviation + molarHeatCapacity: 40 heatCapacityRatio: 1.3 molarMass: 44 - color: 8F00FF + color: '#8F00FF' reagent: NitrousOxide pricePerMole: 0.1 - type: gas id: Frezon - name: gases-frezon - specificHeat: 600 # Strongest by far + name: gas-frezon + abbreviation: gas-frezon-abbreviation + molarHeatCapacity: 600 # Strongest by far heatCapacityRatio: 1.33 molarMass: 50 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi + gasOverlayTexture: /Textures/Effects/atmospherics.rsi gasOverlayState: frezon gasMolesVisible: 0.6 - color: 3a758c + color: '#3a758c' reagent: Frezon pricePerMole: 1 diff --git a/Resources/Prototypes/_Adventure/Gases/gas.yml b/Resources/Prototypes/_Adventure/Gases/gas.yml deleted file mode 100644 index fceec786273..00000000000 --- a/Resources/Prototypes/_Adventure/Gases/gas.yml +++ /dev/null @@ -1,135 +0,0 @@ -- type: gas - id: BZ - name: gases-bz - specificHeat: 20 - heatCapacityRatio: 1.33 - molarMass: 100 - color: c56091 - reagent: BZ - pricePerMole: 4 - -- type: gas - id: Halon - name: gases-halon - specificHeat: 175 - heatCapacityRatio: 1.33 - molarMass: 150 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: halon - color: 99ccff - reagent: Halon - pricePerMole: 16 - -- type: gas - id: Healium - name: gases-healium - specificHeat: 10 - heatCapacityRatio: 1.33 - molarMass: 40 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: healium - color: 512525 - reagent: Healium - pricePerMole: 24 - -- type: gas - id: HyperNoblium - name: gases-hyper-noblium - specificHeat: 2000 - heatCapacityRatio: 1.33 - molarMass: 150 - gasOverlaySprite: /Textures/Effects/atmospherics.rsi - gasOverlayState: frezon - gasMolesVisible: 0.1 - gasVisbilityFactor: 1000 - color: 0066ff - reagent: HyperNoblium - pricePerMole: 30 - -- type: gas - id: Hydrogen - name: gases-hydrogen - specificHeat: 15 - heatCapacityRatio: 1.5 - molarMass: 2 - color: e1e1e1 - reagent: Hydrogen - pricePerMole: 10 - -- type: gas - id: Pluoxium - name: gases-pluoxium - specificHeat: 80 - heatCapacityRatio: 1.5 - molarMass: 44 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: halon - gasVisbilityFactor: 1000 - color: 25aef7 - reagent: Pluoxium - pricePerMole: 10 - -- type: gas - id: Nitrium - name: gases-nitrium - specificHeat: 10 - heatCapacityRatio: 1.3 - molarMass: 50 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: nitrium - gasVisbilityFactor: 500 - color: b65d40 - reagent: Nitrium - pricePerMole: 24 - -- type: gas - id: Helium - name: gases-helium - specificHeat: 15 - heatCapacityRatio: 15 - molarMass: 4 - color: acac00 - reagent: Helium - pricePerMole: 14 - -- type: gas - id: AntiNoblium - name: gases-anti-noblium - specificHeat: 1 - heatCapacityRatio: 1 - molarMass: 200 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: anti_noblium - gasMolesVisible: 0.1 - gasVisbilityFactor: 100 - color: 003000 - reagent: AntiNoblium - pricePerMole: 40 - -- type: gas - id: ProtoNitrate - name: gases-proto-nitrate - specificHeat: 30 - heatCapacityRatio: 1.33 - molarMass: 120 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: proto_nitrate - gasMolesVisible: 0.1 - gasVisbilityFactor: 800 - color: 00cd0d - reagent: ProtoNitrate - pricePerMole: 10 - -- type: gas - id: Zauker - name: gases-zauker - specificHeat: 350 - heatCapacityRatio: 1.33 - molarMass: 110 - gasOverlaySprite: /Textures/_Adventure/Effects/atmospherics.rsi - gasOverlayState: zauker - gasMolesVisible: 0.1 - gasVisbilityFactor: 100 - color: 000031 - reagent: Zauker - pricePerMole: 3000 diff --git a/Resources/Prototypes/_Adventure/Gases/gases.yml b/Resources/Prototypes/_Adventure/Gases/gases.yml new file mode 100644 index 00000000000..4502b26df1c --- /dev/null +++ b/Resources/Prototypes/_Adventure/Gases/gases.yml @@ -0,0 +1,154 @@ +- type: gas + id: BZ + name: gas-bz + abbreviation: gas-bz-abbreviation + molarHeatCapacity: 20 + heatCapacityRatio: 1.3 + molarMass: 100 + color: '#9370db' + reagent: BZ + pricePerMole: 3 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: bz + +- type: gas + id: Healium + name: gas-healium + abbreviation: gas-healium-abbreviation + molarHeatCapacity: 10 + heatCapacityRatio: 1.3 + molarMass: 40 + gasMolesVisible: 0.1 + gasVisibilityFactor: 500 + color: '#8b0000' + reagent: Healium + pricePerMole: 12 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: healium + +- type: gas + id: Nitrium + name: gas-nitrium + abbreviation: gas-nitrium-abbreviation + molarHeatCapacity: 10 + heatCapacityRatio: 1.3 + molarMass: 60 + gasMolesVisible: 0.1 + gasVisibilityFactor: 500 + color: '#8B4513' + reagent: Nitrium + pricePerMole: 12 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: nitrium + +- type: gas + id: Pluoxium + name: gas-pluoxium + abbreviation: gas-pluoxium-abbreviation + molarHeatCapacity: 80 + heatCapacityRatio: 1.3 + molarMass: 40 + color: '#0054AA' + reagent: Pluoxium + pricePerMole: 5 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: pluoxium + +- type: gas + id: Hydrogen + name: gas-hydrogen + abbreviation: gas-hydrogen-abbreviation + molarHeatCapacity: 15 + heatCapacityRatio: 1.4 + molarMass: 2 + color: '#FFFFFF' + reagent: Hydrogen + pricePerMole: 5 + isFuel: true + +- type: gas + id: HyperNoblium + name: gas-hyper-noblium + abbreviation: gas-hyper-noblium-abbreviation + molarHeatCapacity: 2000 + heatCapacityRatio: 1.3 + molarMass: 150 + gasMolesVisible: 0.1 + gasVisibilityFactor: 1000 + color: '#33cccc' + reagent: Hyper-Noblium + pricePerMole: 15 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: hyper_noblium + +- type: gas + id: ProtoNitrate + name: gas-proto-nitrate + abbreviation: gas-proto-nitrate-abbreviation + molarHeatCapacity: 30 + heatCapacityRatio: 1.3 + molarMass: 120 + gasMolesVisible: 0.1 + gasVisibilityFactor: 1000 + color: '#009933' + reagent: Proto-Nitrate + pricePerMole: 5 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: proto_nitrate + +- type: gas + id: Zauker + name: gas-zauker + abbreviation: gas-zauker-abbreviation + molarHeatCapacity: 350 + heatCapacityRatio: 1.3 + molarMass: 110 + gasMolesVisible: 0.1 + gasVisibilityFactor: 250 + color: '#1c1a1a' + reagent: Zauker + pricePerMole: 15 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: zauker + +- type: gas + id: Halon + name: gas-halon + abbreviation: gas-halon-abbreviation + molarHeatCapacity: 1.4 + heatCapacityRatio: 1.3 + molarMass: 150 + gasMolesVisible: 0.1 + gasVisibilityFactor: 500 + color: '#e3574d' + reagent: Halon + pricePerMole: 8 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: halon + +- type: gas + id: Helium + name: gas-helium + abbreviation: gas-helium-abbreviation + molarHeatCapacity: 15 + heatCapacityRatio: 20 + molarMass: 4 + color: '#005959' + reagent: Helium + pricePerMole: 7 + isFuel: true + +- type: gas + id: AntiNoblium + name: gas-anti-noblium + abbreviation: gas-anti-noblium-abbreviation + molarHeatCapacity: 1 + heatCapacityRatio: 1 + molarMass: 200 + gasMolesVisible: 0.1 + gasVisibilityFactor: 100 + color: '#525151' + reagent: Anti-Noblium + pricePerMole: 20 + gasOverlayTexture: /Textures/_Adventure/Effects/atmospherics.rsi + gasOverlayState: anti_noblium diff --git a/Resources/Prototypes/_Adventure/Gases/reactions.yml b/Resources/Prototypes/_Adventure/Gases/reactions.yml index 82c2e86ba45..f0689be8e6b 100644 --- a/Resources/Prototypes/_Adventure/Gases/reactions.yml +++ b/Resources/Prototypes/_Adventure/Gases/reactions.yml @@ -33,7 +33,7 @@ - 0 # bz - 0.01 # halon effects: - - !type:HalonFireSuppressionReaction {} + - !type:HalonOxygenAbsorptionReaction {} - type: gasReaction id: HealiumProduction @@ -91,7 +91,7 @@ - 0 # hyper-noblium - 0.02 # hydrogen effects: - - !type:HydrogenBurnReaction {} + - !type:HydrogenFireReaction {} - type: gasReaction id: PluoxiumProduction @@ -114,7 +114,7 @@ - 0 # hyper-noblium - 0 # hydrogen effects: - - !type:PluoxiumProductionReaction {} + - !type:PluoxiumProductionReaction {} - type: gasReaction id: NitriumProduction @@ -136,7 +136,7 @@ - 0 # hyper-noblium - 0 # hydrogen effects: - - !type:NitriumProductionReaction {} + - !type:NitriumProductionReaction {} - type: gasReaction id: NitriumDecomposition @@ -164,7 +164,7 @@ - 0 # protonitrate - 0 # zauker effects: - - !type:NitriumDecompositionReaction {} + - !type:NitriumDecompositionReaction {} - type: gasReaction id: PNProduction @@ -188,7 +188,7 @@ - 0.02 # hydrogen - 0.01 # pluoxium effects: - - !type:ProtoNitrateProductionReaction {} + - !type:ProtoNitrateProductionReaction {} - type: gasReaction id: PNHydrogenConversion @@ -214,7 +214,7 @@ - 0 # antinob - 0.01 # protonitrate effects: - - !type:ProtoNitrateHydrogenConversion {} + - !type:ProtoNitrateHydrogenConversionReaction {} - type: gasReaction id: PNTritiumDetox @@ -242,7 +242,7 @@ - 0 # antinob - 0.01 # protonitrate effects: - - !type:ProtoNitrateTritiumReaction {} + - !type:ProtoNitrateTritiumConversionReaction {} - type: gasReaction id: PNBZDecomposition @@ -270,7 +270,7 @@ - 0 # antinob - 0.01 # protonitrate effects: - - !type:ProtoNitrateBZDecomposition {} + - !type:ProtoNitrateBZaseConversionReaction {} - type: gasReaction id: ZaukerProduction @@ -295,7 +295,7 @@ - 0 # pluoxium - 0.01 # nitrium effects: - - !type:ZaukerProductionReaction {} + - !type:ZaukerProductionReaction {} - type: gasReaction id: ZaukerDecomposition @@ -322,4 +322,4 @@ - 0 # protonitrate - 0.01 # zauker effects: - - !type:ZaukerDecompositionReaction {} + - !type:ZaukerDecompositionReaction {} diff --git a/Resources/Prototypes/_Adventure/Gases/reagents.yml b/Resources/Prototypes/_Adventure/Gases/reagents.yml index 5552fc9436c..c7ce4968ce9 100644 --- a/Resources/Prototypes/_Adventure/Gases/reagents.yml +++ b/Resources/Prototypes/_Adventure/Gases/reagents.yml @@ -316,7 +316,7 @@ meltingPoint: -259.2 - type: reagent - id: ProtoNitrate + id: Proto-Nitrate name: reagent-name-proto-nitrate desc: reagent-desc-proto-nitrate physicalDesc: reagent-physical-desc-gaseous diff --git a/Resources/Textures/_Adventure/Effects/atmospherics.rsi/meta.json b/Resources/Textures/_Adventure/Effects/atmospherics.rsi/meta.json index 359e98841da..7585e455c63 100644 --- a/Resources/Textures/_Adventure/Effects/atmospherics.rsi/meta.json +++ b/Resources/Textures/_Adventure/Effects/atmospherics.rsi/meta.json @@ -8,32 +8,28 @@ }, "states": [ { - "name": "nitryl", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "name": "healium", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] }, { "name": "nitrium", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] }, { - "name": "proto_nitrate", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] - }, - { - "name": "zauker", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "name": "halon", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] }, { "name": "anti_noblium", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] }, { - "name": "healium", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "name": "zauker", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] }, { - "name": "halon", - "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + "name": "proto_nitrate", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]] } ] } diff --git a/Resources/Textures/_Adventure/Effects/atmospherics.rsi/nitryl.png b/Resources/Textures/_Adventure/Effects/atmospherics.rsi/nitryl.png deleted file mode 100644 index 4fa5e1d231d..00000000000 Binary files a/Resources/Textures/_Adventure/Effects/atmospherics.rsi/nitryl.png and /dev/null differ