using Content.Server.Atmos.EntitySystems; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.GameTicking.Components; using Robust.Shared.Audio; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.StationEvents.Events { internal sealed class GasLeakRule : StationEventSystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; protected override void Started(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); if (!TryComp(uid, out var stationEvent)) return; // 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 component.TargetTile, out var target, out component.TargetGrid, out component.TargetCoords)) { component.TargetStation = target.Value; component.FoundTile = true; component.LeakGas = RobustRandom.Pick(component.LeakableGases); // Was 50-50 on using normal distribution. var totalGas = RobustRandom.Next(component.MinimumGas, component.MaximumGas); component.MolesPerSecond = RobustRandom.Next(component.MinimumMolesPerSecond, component.MaximumMolesPerSecond); if (gameRule.Delay is {} startAfter) stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.Next(RobustRandom)); } // 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. } protected override void ActiveTick(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, float frameTime) { base.ActiveTick(uid, component, gameRule, frameTime); component.TimeUntilLeak -= frameTime; if (component.TimeUntilLeak > 0f) return; component.TimeUntilLeak += component.LeakCooldown; if (!component.FoundTile || component.TargetGrid == default || Deleted(component.TargetGrid) || !_atmosphere.IsSimulatedGrid(component.TargetGrid)) { ForceEndSelf(uid, gameRule); return; } var environment = _atmosphere.GetTileMixture(component.TargetGrid, null, component.TargetTile, true); environment?.AdjustMoles(component.LeakGas, component.LeakCooldown * component.MolesPerSecond); } protected override void Ended(EntityUid uid, GasLeakRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) { base.Ended(uid, component, gameRule, args); Spark(uid, component); } private void Spark(EntityUid uid, GasLeakRuleComponent component) { if (RobustRandom.NextFloat() <= component.SparkChance) { if (!component.FoundTile || component.TargetGrid == default || (!Exists(component.TargetGrid) ? EntityLifeStage.Deleted : MetaData(component.TargetGrid).EntityLifeStage) >= EntityLifeStage.Deleted || !_atmosphere.IsSimulatedGrid(component.TargetGrid)) { return; } // 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. _atmosphere.HotspotExpose(component.TargetGrid, component.TargetTile, 700f, 50f, null, true); Audio.PlayPvs(new SoundPathSpecifier("/Audio/Effects/sparks4.ogg"), component.TargetCoords); } } } }