diff --git a/Content.Server/StationEvents/GasLeak.cs b/Content.Server/StationEvents/GasLeak.cs new file mode 100644 index 0000000000..86571b9acd --- /dev/null +++ b/Content.Server/StationEvents/GasLeak.cs @@ -0,0 +1,202 @@ +using Content.Server.GameObjects.Components.Atmos; +using Content.Server.Interfaces.GameTicking; +using Content.Shared.Atmos; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.Map; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; + +#nullable enable +namespace Content.Server.StationEvents +{ + internal sealed class GasLeak : StationEvent + { + public override string Name => "GasLeak"; + + public override string? StartAnnouncement => + "Attention crew, there is a gas leak on the station. We advise you to avoid the area and wear suit internals in the meantime."; + + // Sourced from https://github.com/vgstation-coders/vgstation13/blob/2c5a491446ab824a8fbbf39bcf656b590e0228df/sound/misc/bloblarm.ogg + public override string? StartAudio => "/Audio/Announcements/bloblarm.ogg"; + + protected override string? EndAnnouncement => "The source of the gas leak has been fixed. Please be cautious around areas with gas remaining."; + + private static readonly Gas[] LeakableGases = { + Gas.Phoron, + Gas.Tritium, + }; + + public override int EarliestStart => 10; + + public override int MinimumPlayers => 5; + + /// + /// Give people time to get their internals on. + /// + protected override float StartAfter => 20f; + + /// + /// Don't know how long the event will be until we calculate the leak amount. + /// + protected override float EndAfter { get; set; } = float.MaxValue; + + /// + /// Running cooldown of how much time until another leak. + /// + private float _timeUntilLeak; + + /// + /// How long between more gas being added to the tile. + /// + private const float LeakCooldown = 1.0f; + + // Event variables + + private IEntity? _targetGrid; + + private Vector2i _targetTile; + + private EntityCoordinates _targetCoords; + + private bool _foundTile; + + private Gas _leakGas; + + private float _molesPerSecond; + + private const int MinimumMolesPerSecond = 20; + + /// + /// Don't want to make it too fast to give people time to flee. + /// + private const int MaximumMolesPerSecond = 50; + + private const int MinimumGas = 250; + + private const int MaximumGas = 1000; + + private const float SparkChance = 0.05f; + + public override void Startup() + { + base.Startup(); + var robustRandom = IoCManager.Resolve(); + + // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there. + if (TryFindRandomTile(out _targetTile, robustRandom)) + { + _foundTile = true; + + _leakGas = robustRandom.Pick(LeakableGases); + // Was 50-50 on using normal distribution. + var totalGas = (float) robustRandom.Next(MinimumGas, MaximumGas); + _molesPerSecond = robustRandom.Next(MinimumMolesPerSecond, MaximumMolesPerSecond); + EndAfter = totalGas / _molesPerSecond + StartAfter; + Logger.InfoS("stationevents", $"Leaking {totalGas} of {_leakGas} over {EndAfter - StartAfter} seconds at {_targetTile}"); + } + + // Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement + // there just to fuck with people even if there is no valid tile is funny. + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!Started || !Running) return; + + _timeUntilLeak -= frameTime; + + if (_timeUntilLeak > 0f) return; + _timeUntilLeak += LeakCooldown; + + if (!_foundTile || + _targetGrid == null || + _targetGrid.Deleted || + !_targetGrid.TryGetComponent(out GridAtmosphereComponent? gridAtmos)) + { + Running = false; + return; + } + + var atmos = gridAtmos.GetTile(_targetTile); + + atmos?.Air?.AdjustMoles(_leakGas, LeakCooldown * _molesPerSecond); + atmos?.Invalidate(); + } + + public override void Shutdown() + { + base.Shutdown(); + + Spark(); + + _foundTile = false; + _targetGrid = null; + _targetTile = default; + _targetCoords = default; + _leakGas = Gas.Oxygen; + EndAfter = float.MaxValue; + } + + private void Spark() + { + var robustRandom = IoCManager.Resolve(); + if (robustRandom.NextFloat() <= SparkChance) + { + if (!_foundTile || + _targetGrid == null || + _targetGrid.Deleted || + !_targetGrid.TryGetComponent(out GridAtmosphereComponent? gridAtmos)) + { + return; + } + + var atmos = gridAtmos.GetTile(_targetTile); + // Don't want it to be so obnoxious as to instantly murder anyone in the area but enough that + // it COULD start potentially start a bigger fire. + atmos?.HotspotExpose(700f, 50f, true); + EntitySystem.Get().PlayAtCoords("/Audio/Effects/sparks4.ogg", _targetCoords); + } + } + + private bool TryFindRandomTile(out Vector2i tile, IRobustRandom? robustRandom = null) + { + tile = default; + var defaultGridId = IoCManager.Resolve().DefaultGridId; + + if (!IoCManager.Resolve().TryGetGrid(defaultGridId, out var grid) || + !IoCManager.Resolve().TryGetEntity(grid.GridEntityId, out _targetGrid)) return false; + + _targetGrid.EnsureComponent(out GridAtmosphereComponent gridAtmos); + robustRandom ??= IoCManager.Resolve(); + var found = false; + var gridBounds = grid.WorldBounds; + var gridPos = grid.WorldPosition; + + for (var i = 0; i < 10; i++) + { + var randomX = robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right); + var randomY = robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top); + + tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y); + if (gridAtmos.IsSpace(tile) || gridAtmos.IsAirBlocked(tile)) continue; + found = true; + _targetCoords = grid.GridTileToLocal(tile); + break; + } + + if (!found) return false; + + return true; + } + } +} diff --git a/Resources/Audio/Announcements/bloblarm.ogg b/Resources/Audio/Announcements/bloblarm.ogg new file mode 100644 index 0000000000..50ba29739f Binary files /dev/null and b/Resources/Audio/Announcements/bloblarm.ogg differ