using Content.Server.Atmos.Components; using Content.Server.Decals; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Reactions; using Content.Shared.Database; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Atmos.EntitySystems { public sealed partial class AtmosphereSystem { private static readonly ProtoId DefaultHotspotSounds = "AtmosHotspot"; [Dependency] private readonly DecalSystem _decalSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; private const int HotspotSoundCooldownCycles = 200; private int _hotspotSoundCooldown = 0; [ViewVariables(VVAccess.ReadWrite)] public SoundSpecifier? HotspotSound { get; private set; } = new SoundCollectionSpecifier(DefaultHotspotSounds); private void ProcessHotspot( Entity ent, TileAtmosphere tile) { var gridAtmosphere = ent.Comp1; if (!tile.Hotspot.Valid) { gridAtmosphere.HotspotTiles.Remove(tile); return; } AddActiveTile(gridAtmosphere, tile); if (!tile.Hotspot.SkippedFirstProcess) { tile.Hotspot.SkippedFirstProcess = true; return; } if(tile.ExcitedGroup != null) ExcitedGroupResetCooldowns(tile.ExcitedGroup); 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.Hotspot = new Hotspot(); InvalidateVisuals(ent, tile); return; } PerformHotspotExposure(tile); if (tile.Hotspot.Bypassing) { tile.Hotspot.State = 3; var gridUid = ent.Owner; var tilePos = tile.GridIndices; // Get the existing decals on the tile var tileDecals = _decalSystem.GetDecalsInRange(gridUid, tilePos); // Count the burnt decals on the tile var tileBurntDecals = 0; foreach (var set in tileDecals) { if (Array.IndexOf(_burntDecals, set.Decal.Id) == -1) continue; tileBurntDecals++; if (tileBurntDecals > 4) break; } // Add a random burned decal to the tile only if there are less than 4 of them if (tileBurntDecals < 4) _decalSystem.TryAddDecal(_burntDecals[_random.Next(_burntDecals.Length)], new EntityCoordinates(gridUid, tilePos), out _, cleanable: true); if (tile.Air.Temperature > Atmospherics.FireMinimumTemperatureToSpread) { var radiatedTemperature = tile.Air.Temperature * Atmospherics.FireSpreadRadiosityScale; foreach (var otherTile in tile.AdjacentTiles) { // TODO ATMOS: This is sus. Suss this out. if (otherTile == null) continue; if(!otherTile.Hotspot.Valid) HotspotExpose(gridAtmosphere, otherTile, radiatedTemperature, Atmospherics.CellVolume/4); } } } else { tile.Hotspot.State = (byte) (tile.Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1); } if (tile.Hotspot.Temperature > tile.MaxFireTemperatureSustained) tile.MaxFireTemperatureSustained = tile.Hotspot.Temperature; if (_hotspotSoundCooldown++ == 0 && HotspotSound != null) { var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices); // A few details on the audio parameters for fire. // The greater the fire state, the lesser the pitch variation. // The greater the fire state, the greater the volume. _audio.PlayPvs(HotspotSound, coordinates, HotspotSound.Params.WithVariation(0.15f / tile.Hotspot.State).WithVolume(-5f + 5f * tile.Hotspot.State)); } if (_hotspotSoundCooldown > HotspotSoundCooldownCycles) _hotspotSoundCooldown = 0; // TODO ATMOS Maybe destroy location here? } private void HotspotExpose(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, float exposedTemperature, float exposedVolume, bool soh = false, EntityUid? sparkSourceUid = null) { if (tile.Air == null) return; var oxygen = tile.Air.GetMoles(Gas.Oxygen); if (oxygen < 0.5f) return; var plasma = tile.Air.GetMoles(Gas.Plasma); var tritium = tile.Air.GetMoles(Gas.Tritium); if (tile.Hotspot.Valid) { if (soh) { if (plasma > 0.5f || tritium > 0.5f) { if (tile.Hotspot.Temperature < exposedTemperature) tile.Hotspot.Temperature = exposedTemperature; if (tile.Hotspot.Volume < exposedVolume) tile.Hotspot.Volume = exposedVolume; } } return; } if ((exposedTemperature > Atmospherics.PlasmaMinimumBurnTemperature) && (plasma > 0.5f || tritium > 0.5f)) { 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"); tile.Hotspot = new Hotspot { Volume = exposedVolume * 25f, Temperature = exposedTemperature, SkippedFirstProcess = tile.CurrentCycle > gridAtmosphere.UpdateCounter, Valid = true, State = 1 }; AddActiveTile(gridAtmosphere, tile); gridAtmosphere.HotspotTiles.Add(tile); } } private void PerformHotspotExposure(TileAtmosphere tile) { if (tile.Air == null || !tile.Hotspot.Valid) return; tile.Hotspot.Bypassing = tile.Hotspot.SkippedFirstProcess && tile.Hotspot.Volume > tile.Air.Volume*0.95f; if (tile.Hotspot.Bypassing) { tile.Hotspot.Volume = tile.Air.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate; tile.Hotspot.Temperature = tile.Air.Temperature; } else { var affected = tile.Air.RemoveVolume(tile.Hotspot.Volume); affected.Temperature = tile.Hotspot.Temperature; React(affected, tile); tile.Hotspot.Temperature = affected.Temperature; tile.Hotspot.Volume = affected.ReactionResults[(byte)GasReaction.Fire] * Atmospherics.FireGrowthRate; Merge(tile.Air, affected); } var fireEvent = new TileFireEvent(tile.Hotspot.Temperature, tile.Hotspot.Volume); _entSet.Clear(); _lookup.GetLocalEntitiesIntersecting(tile.GridIndex, tile.GridIndices, _entSet, 0f); foreach (var entity in _entSet) { RaiseLocalEvent(entity, ref fireEvent); } } } }