]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
A return to foam (foam rework) (#20831)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Fri, 27 Oct 2023 02:52:11 +0000 (22:52 -0400)
committerGitHub <noreply@github.com>
Fri, 27 Oct 2023 02:52:11 +0000 (13:52 +1100)
Content.Client/Chemistry/Visualizers/FoamVisualizerSystem.cs
Content.Client/Chemistry/Visualizers/FoamVisualsComponent.cs
Content.Server/Body/Systems/InternalsSystem.cs
Content.Server/Chemistry/Components/SmokeComponent.cs [deleted file]
Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs
Content.Server/Fluids/EntitySystems/SmokeSystem.cs
Content.Server/StationEvents/Components/VentClogRuleComponent.cs
Content.Server/StationEvents/Events/VentClogRule.cs
Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs
Content.Shared/Chemistry/Components/SmokeAffectedComponent.cs [new file with mode: 0644]
Content.Shared/Chemistry/Components/SmokeComponent.cs [new file with mode: 0644]

index ec582ee0944a8ce157281f0e972e4ab244410073..2ee88956ff60f01053616f5010984e37e42018d0 100644 (file)
@@ -1,8 +1,6 @@
-using Content.Shared.Smoking;
-using Robust.Shared.Spawners;
+using Content.Shared.Chemistry.Components;
 using Robust.Client.Animations;
 using Robust.Client.GameObjects;
-using Robust.Shared.Network;
 using Robust.Shared.Timing;
 
 namespace Content.Client.Chemistry.Visualizers;
@@ -18,6 +16,7 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
     {
         base.Initialize();
         SubscribeLocalEvent<FoamVisualsComponent, ComponentInit>(OnComponentInit);
+        SubscribeLocalEvent<FoamVisualsComponent, AnimationCompletedEvent>(OnAnimationComplete);
     }
 
     public override void Update(float frameTime)
@@ -27,11 +26,11 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
         if (!_timing.IsFirstTimePredicted)
             return;
 
-        var query = EntityQueryEnumerator<FoamVisualsComponent, TimedDespawnComponent>();
+        var query = EntityQueryEnumerator<FoamVisualsComponent, SmokeComponent>();
 
-        while (query.MoveNext(out var uid, out var comp, out var despawn))
+        while (query.MoveNext(out var uid, out var comp, out var smoke))
         {
-            if (despawn.Lifetime > 1f)
+            if (_timing.CurTime < comp.StartTime + TimeSpan.FromSeconds(smoke.Duration) - TimeSpan.FromSeconds(comp.AnimationTime))
                 continue;
 
             // Despawn animation.
@@ -48,6 +47,7 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
     /// </summary>
     private void OnComponentInit(EntityUid uid, FoamVisualsComponent comp, ComponentInit args)
     {
+        comp.StartTime = _timing.CurTime;
         comp.Animation = new Animation
         {
             Length = TimeSpan.FromSeconds(comp.AnimationTime),
@@ -58,12 +58,21 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
                     LayerKey = FoamVisualLayers.Base,
                     KeyFrames =
                     {
-                        new AnimationTrackSpriteFlick.KeyFrame(comp.State, 0f)
+                        new AnimationTrackSpriteFlick.KeyFrame(comp.AnimationState, 0f)
                     }
                 }
             }
         };
     }
+
+    private void OnAnimationComplete(EntityUid uid, FoamVisualsComponent component, AnimationCompletedEvent args)
+    {
+        if (args.Key != FoamVisualsComponent.AnimationKey)
+            return;
+
+        if (TryComp<SpriteComponent>(uid, out var sprite))
+            sprite.Visible = false;
+    }
 }
 
 public enum FoamVisualLayers : byte
index b09c74aa0feca660f86795eab8b8a73efe277da1..8199efa42ee2dd4bca3aa0b90923a2df64bb6464 100644 (file)
@@ -1,5 +1,6 @@
 using Robust.Client.Animations;
 using Robust.Client.Graphics;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Client.Chemistry.Visualizers;
 
@@ -15,18 +16,21 @@ public sealed partial class FoamVisualsComponent : Component
     /// </summary>
     public const string AnimationKey = "foamdissolve_animation";
 
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan StartTime;
+
     /// <summary>
     /// How long the foam visually dissolves for.
     /// </summary>
-    [DataField("animationTime")]
-    public float AnimationTime = 0.6f;
+    [DataField]
+    public float AnimationTime = 0.5f;
 
     /// <summary>
     /// The state of the entities base sprite RSI that is displayed when the foam dissolves.
     /// Cannot use <see cref="RSI.StateKey"/> because it does not have <see cref="DataDefinitionAttribute"/> and I am not making an engine PR at this time.
     /// </summary>
-    [DataField("animationState")]
-    public string State = "foam-dissolve";
+    [DataField]
+    public string AnimationState = "foam-dissolve";
 
     /// <summary>
     /// The animation used while the foam dissolves.
index ec4faa345cdde1966d0e66e2583273a35bcdc0d6..17a6544976b98c97035542f73b0868ff01b585cb 100644 (file)
@@ -197,6 +197,14 @@ public sealed class InternalsSystem : EntitySystem
         return true;
     }
 
+    public bool AreInternalsWorking(EntityUid uid, InternalsComponent? component = null)
+    {
+        if (!Resolve(uid, ref component, false))
+            return false;
+
+        return AreInternalsWorking(component);
+    }
+
     public bool AreInternalsWorking(InternalsComponent component)
     {
         return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) &&
diff --git a/Content.Server/Chemistry/Components/SmokeComponent.cs b/Content.Server/Chemistry/Components/SmokeComponent.cs
deleted file mode 100644 (file)
index 133ad41..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-using Content.Shared.Fluids.Components;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Chemistry.Components;
-
-/// <summary>
-/// Stores solution on an anchored entity that has touch and ingestion reactions
-/// to entities that collide with it. Similar to <see cref="PuddleComponent"/>
-/// </summary>
-[RegisterComponent]
-public sealed partial class SmokeComponent : Component
-{
-    public const string SolutionName = "solutionArea";
-
-    [DataField("nextReact", customTypeSerializer:typeof(TimeOffsetSerializer))]
-    public TimeSpan NextReact = TimeSpan.Zero;
-
-    [DataField("spreadAmount")]
-    public int SpreadAmount = 0;
-
-    /// <summary>
-    ///     Have we reacted with our tile yet?
-    /// </summary>
-    [DataField("reactedTile")]
-    public bool ReactedTile = false;
-}
index e6eaab8a59bb8496c882816ca9dd7d10b6b2ac5c..291a654422ebfe0e1d1f667856e505643c3b57aa 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Chemistry.Components;
 using Content.Server.Fluids.EntitySystems;
 using Content.Shared.Audio;
 using Content.Shared.Chemistry.EntitySystems;
@@ -10,7 +9,6 @@ using Content.Shared.Maps;
 using JetBrains.Annotations;
 using Robust.Shared.Audio;
 using Robust.Shared.Map;
-using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
@@ -71,18 +69,10 @@ namespace Content.Server.Chemistry.ReactionEffects
             var coords = grid.MapToGrid(transform.MapPosition);
             var ent = args.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid());
 
-            if (!args.EntityManager.TryGetComponent<SmokeComponent>(ent, out var smokeComponent))
-            {
-                Logger.Error("Couldn't get AreaEffectComponent from " + _prototypeId);
-                args.EntityManager.QueueDeleteEntity(ent);
-                return;
-            }
-
             var smoke = args.EntityManager.System<SmokeSystem>();
-            smokeComponent.SpreadAmount = spreadAmount;
-            smoke.Start(ent, smokeComponent, splitSolution, _duration);
+            smoke.StartSmoke(ent, splitSolution, _duration, spreadAmount);
 
-            SoundSystem.Play(_sound.GetSound(), Filter.Pvs(args.SolutionEntity), args.SolutionEntity, AudioHelpers.WithVariation(0.125f));
+            args.EntityManager.System<SharedAudioSystem>().PlayPvs(_sound, args.SolutionEntity, AudioHelpers.WithVariation(0.125f));
         }
     }
 }
index f7732fec62f04604f28ed5cd7dcd8e449ff5632d..5459dacf0b912cf5ba26d07f616f7496e7314621 100644 (file)
@@ -1,14 +1,23 @@
 using System.Linq;
-using Content.Server.Chemistry.Components;
+using Content.Server.Administration.Logs;
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
 using Content.Server.Chemistry.ReactionEffects;
 using Content.Server.Spreader;
+using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Database;
 using Content.Shared.FixedPoint;
 using Content.Shared.Smoking;
 using Robust.Server.GameObjects;
 using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Physics.Systems;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
@@ -22,70 +31,140 @@ namespace Content.Server.Fluids.EntitySystems;
 public sealed class SmokeSystem : EntitySystem
 {
     // If I could do it all again this could probably use a lot more of puddles.
+    [Dependency] private readonly IAdminLogManager _logger = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPrototypeManager _prototype = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly AppearanceSystem _appearance = default!;
-    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly BloodstreamSystem _blood = default!;
+    [Dependency] private readonly InternalsSystem _internals = default!;
+    [Dependency] private readonly ReactiveSystem _reactive = default!;
+    [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
     [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly TransformSystem _transform = default!;
+
+    private EntityQuery<SmokeComponent> _smokeQuery;
+    private EntityQuery<SmokeAffectedComponent> _smokeAffectedQuery;
 
     /// <inheritdoc/>
     public override void Initialize()
     {
         base.Initialize();
-        SubscribeLocalEvent<SmokeComponent, EntityUnpausedEvent>(OnSmokeUnpaused);
+
+        _smokeQuery = GetEntityQuery<SmokeComponent>();
+        _smokeAffectedQuery = GetEntityQuery<SmokeAffectedComponent>();
+
+        SubscribeLocalEvent<SmokeComponent, StartCollideEvent>(OnStartCollide);
+        SubscribeLocalEvent<SmokeComponent, EndCollideEvent>(OnEndCollide);
         SubscribeLocalEvent<SmokeComponent, ReactionAttemptEvent>(OnReactionAttempt);
         SubscribeLocalEvent<SmokeComponent, SpreadNeighborsEvent>(OnSmokeSpread);
+        SubscribeLocalEvent<SmokeAffectedComponent, EntityUnpausedEvent>(OnAffectedUnpaused);
+    }
+
+    /// <inheritdoc/>
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<SmokeAffectedComponent>();
+        var curTime = _timing.CurTime;
+        while (query.MoveNext(out var uid, out var smoke))
+        {
+            if (curTime < smoke.NextSecond)
+                continue;
+
+            smoke.NextSecond += TimeSpan.FromSeconds(1);
+            SmokeReact(uid, smoke.SmokeEntity);
+        }
+    }
+
+    private void OnStartCollide(EntityUid uid, SmokeComponent component, ref StartCollideEvent args)
+    {
+        if (_smokeAffectedQuery.HasComponent(args.OtherEntity))
+            return;
+
+        var smokeAffected = AddComp<SmokeAffectedComponent>(args.OtherEntity);
+        smokeAffected.SmokeEntity = uid;
+        smokeAffected.NextSecond = _timing.CurTime + TimeSpan.FromSeconds(1);
+    }
+
+    private void OnEndCollide(EntityUid uid, SmokeComponent component, ref EndCollideEvent args)
+    {
+        // if we are already in smoke, make sure the thing we are exiting is the current smoke we are in.
+        if (_smokeAffectedQuery.TryGetComponent(args.OtherEntity, out var smokeAffectedComponent))
+        {
+            if (smokeAffectedComponent.SmokeEntity != uid)
+                return;
+        }
+
+        var exists = Exists(uid);
+
+        if (!TryComp<PhysicsComponent>(args.OtherEntity, out var body))
+            return;
+
+        foreach (var ent in _physics.GetContactingEntities(args.OtherEntity, body))
+        {
+            if (exists && ent == uid)
+                continue;
+
+            if (!_smokeQuery.HasComponent(ent))
+                continue;
+
+            smokeAffectedComponent ??= EnsureComp<SmokeAffectedComponent>(args.OtherEntity);
+            smokeAffectedComponent.SmokeEntity = ent;
+            return; // exit the function so we don't remove the component.
+        }
+
+        if (smokeAffectedComponent != null)
+            RemComp(args.OtherEntity, smokeAffectedComponent);
+    }
+
+    private void OnAffectedUnpaused(EntityUid uid, SmokeAffectedComponent component, ref EntityUnpausedEvent args)
+    {
+        component.NextSecond += args.PausedTime;
     }
 
     private void OnSmokeSpread(EntityUid uid, SmokeComponent component, ref SpreadNeighborsEvent args)
     {
-        if (component.SpreadAmount == 0
-            || !_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution))
+        if (component.SpreadAmount == 0 || !_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution))
         {
             RemCompDeferred<ActiveEdgeSpreaderComponent>(uid);
             return;
         }
 
-        var prototype = MetaData(uid).EntityPrototype;
-
-        if (prototype == null)
+        if (Prototype(uid) is not { } prototype)
         {
             RemCompDeferred<ActiveEdgeSpreaderComponent>(uid);
             return;
         }
 
+        if (!args.NeighborFreeTiles.Any())
+            return;
+
         TryComp<TimedDespawnComponent>(uid, out var timer);
-        _appearance.TryGetData(uid, SmokeVisuals.Color, out var color);
 
         // wtf is the logic behind any of this.
-        var smokePerSpread = 1 + component.SpreadAmount / Math.Max(1, args.NeighborFreeTiles.Count);
+        var smokePerSpread = component.SpreadAmount / Math.Max(1, args.NeighborFreeTiles.Count);
         foreach (var neighbor in args.NeighborFreeTiles)
         {
             var coords = neighbor.Grid.GridTileToLocal(neighbor.Tile);
             var ent = Spawn(prototype.ID, coords);
-            var neighborSmoke = EnsureComp<SmokeComponent>(ent);
-            neighborSmoke.SpreadAmount = Math.Max(0, smokePerSpread - 2); // why - 2? who the fuck knows.
-            component.SpreadAmount--;
-            args.Updates--;
-
-            // Listen this is the old behaviour iunno
-            Start(ent, neighborSmoke, solution.Clone(), timer?.Lifetime ?? 10f);
+            var spreadAmount = Math.Max(0, smokePerSpread);
+            component.SpreadAmount -= args.NeighborFreeTiles.Count();
 
-            if (color != null)
-                _appearance.SetData(ent, SmokeVisuals.Color, color);
+            StartSmoke(ent, solution.Clone(), timer?.Lifetime ?? component.Duration, spreadAmount);
 
             if (component.SpreadAmount == 0)
             {
                 RemCompDeferred<ActiveEdgeSpreaderComponent>(uid);
                 break;
             }
-
-            if (args.Updates <= 0)
-                break;
         }
 
+        args.Updates--;
+
         if (args.NeighborFreeTiles.Count > 0 || args.Neighbors.Count == 0 || component.SpreadAmount < 1)
             return;
 
@@ -100,7 +179,6 @@ public sealed class SmokeSystem : EntitySystem
                 continue;
 
             smoke.SpreadAmount++;
-            args.Updates--;
             component.SpreadAmount--;
             EnsureComp<ActiveEdgeSpreaderComponent>(neighbor);
 
@@ -110,6 +188,7 @@ public sealed class SmokeSystem : EntitySystem
                 break;
             }
         }
+
     }
 
     private void OnReactionAttempt(EntityUid uid, SmokeComponent component, ReactionAttemptEvent args)
@@ -128,101 +207,117 @@ public sealed class SmokeSystem : EntitySystem
         }
     }
 
-    private void OnSmokeUnpaused(EntityUid uid, SmokeComponent component, ref EntityUnpausedEvent args)
+    /// <summary>
+    /// Sets up a smoke component for spreading.
+    /// </summary>
+    public void StartSmoke(EntityUid uid, Solution solution, float duration, int spreadAmount, SmokeComponent? component = null)
     {
-        component.NextReact += args.PausedTime;
-    }
+        if (!Resolve(uid, ref component))
+            return;
 
-    /// <inheritdoc/>
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-        var query = EntityQueryEnumerator<SmokeComponent>();
-        var curTime = _timing.CurTime;
+        component.SpreadAmount = spreadAmount;
+        component.Duration = duration;
+        component.TransferRate = solution.Volume / duration;
+        TryAddSolution(uid, solution);
+        Dirty(uid, component);
+        EnsureComp<ActiveEdgeSpreaderComponent>(uid);
 
-        while (query.MoveNext(out var uid, out var smoke))
+        if (TryComp<PhysicsComponent>(uid, out var body) && TryComp<FixturesComponent>(uid, out var fixtures))
         {
-            if (smoke.NextReact > curTime)
-                continue;
+            var xform = Transform(uid);
+            _physics.SetBodyType(uid, BodyType.Dynamic, fixtures, body, xform);
+            _physics.SetCanCollide(uid, true, manager: fixtures, body: body);
+            _broadphase.RegenerateContacts(uid, body, fixtures, xform);
+        }
 
-            smoke.NextReact += TimeSpan.FromSeconds(1.5);
+        var timer = EnsureComp<TimedDespawnComponent>(uid);
+        timer.Lifetime = duration;
 
-            SmokeReact(uid, 1f, smoke);
-        }
+        // The tile reaction happens here because it only occurs once.
+        ReactOnTile(uid, component);
     }
 
     /// <summary>
-    /// Does the relevant smoke reactions for an entity for the specified exposure duration.
+    /// Does the relevant smoke reactions for an entity.
     /// </summary>
-    public void SmokeReact(EntityUid uid, float frameTime, SmokeComponent? component = null, TransformComponent? xform = null)
+    public void SmokeReact(EntityUid entity, EntityUid smokeUid, SmokeComponent? component = null)
     {
-        if (!Resolve(uid, ref component, ref xform))
+        if (!Resolve(smokeUid, ref component))
             return;
 
-        if (!_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution) ||
+        if (!_solutionSystem.TryGetSolution(smokeUid, SmokeComponent.SolutionName, out var solution) ||
             solution.Contents.Count == 0)
         {
             return;
         }
 
-        if (!_mapManager.TryGetGrid(xform.GridUid, out var mapGrid))
-            return;
+        ReactWithEntity(entity, smokeUid, solution, component);
+        UpdateVisuals(smokeUid);
+    }
 
-        var tile = mapGrid.GetTileRef(xform.Coordinates.ToVector2i(EntityManager, _mapManager));
+    private void ReactWithEntity(EntityUid entity, EntityUid smokeUid, Solution solution, SmokeComponent? component = null)
+    {
+        if (!Resolve(smokeUid, ref component))
+            return;
 
-        var solutionFraction = 1 / Math.Floor(frameTime);
-        var ents = _lookup.GetEntitiesIntersecting(tile, 0f, flags: LookupFlags.Uncontained).ToArray();
+        if (!TryComp<BloodstreamComponent>(entity, out var bloodstream))
+            return;
 
-        foreach (var reagentQuantity in solution.Contents.ToArray())
-        {
-            if (reagentQuantity.Quantity == FixedPoint2.Zero)
-                continue;
+        var blockIngestion =  _internals.AreInternalsWorking(entity);
 
-            // NOOP, react with entities on the tile or whatever.
-        }
+        var cloneSolution = solution.Clone();
+        var availableTransfer = FixedPoint2.Min(cloneSolution.Volume, component.TransferRate);
+        var transferAmount = FixedPoint2.Min(availableTransfer, bloodstream.ChemicalSolution.AvailableVolume);
+        var transferSolution = cloneSolution.SplitSolution(transferAmount);
 
-        foreach (var entity in ents)
+        foreach (var reagentQuantity in transferSolution.Contents.ToArray())
         {
-            if (entity == uid)
+            if (reagentQuantity.Quantity == FixedPoint2.Zero)
                 continue;
+            var reagentProto = _prototype.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
 
-            ReactWithEntity(entity, solution, solutionFraction);
+            _reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentProto, reagentQuantity, transferSolution);
+            if (!blockIngestion)
+                _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentProto, reagentQuantity, transferSolution);
         }
 
-        UpdateVisuals(uid);
-    }
+        if (blockIngestion)
+            return;
 
-    private void UpdateVisuals(EntityUid uid)
-    {
-        if (TryComp(uid, out AppearanceComponent? appearance) &&
-            _solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution))
+        if (_blood.TryAddToChemicals(entity, transferSolution, bloodstream))
         {
-            var color = solution.GetColor(_prototype);
-            _appearance.SetData(uid, SmokeVisuals.Color, color, appearance);
+            // Log solution addition by smoke
+            _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity):target} ingested smoke {SolutionContainerSystem.ToPrettyString(transferSolution)}");
         }
     }
 
-    private void ReactWithEntity(EntityUid entity, Solution solution, double solutionFraction)
+    private void ReactOnTile(EntityUid uid, SmokeComponent? component = null, TransformComponent? xform = null)
     {
-        // NOOP due to people complaining constantly.
-        return;
-    }
+        if (!Resolve(uid, ref component, ref xform))
+            return;
 
-    /// <summary>
-    /// Sets up a smoke component for spreading.
-    /// </summary>
-    public void Start(EntityUid uid, SmokeComponent component, Solution solution, float duration)
-    {
-        TryAddSolution(uid, component, solution);
-        EnsureComp<ActiveEdgeSpreaderComponent>(uid);
-        var timer = EnsureComp<TimedDespawnComponent>(uid);
-        timer.Lifetime = duration;
+        if (!_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution) || !solution.Any())
+            return;
+
+        if (!_mapManager.TryGetGrid(xform.GridUid, out var mapGrid))
+            return;
+
+        var tile = mapGrid.GetTileRef(xform.Coordinates.ToVector2i(EntityManager, _mapManager, _transform));
+
+        foreach (var reagentQuantity in solution.Contents.ToArray())
+        {
+            if (reagentQuantity.Quantity == FixedPoint2.Zero)
+                continue;
+
+            var reagent = _prototype.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
+            reagent.ReactionTile(tile, reagentQuantity.Quantity);
+        }
     }
 
     /// <summary>
     /// Adds the specified solution to the relevant smoke solution.
     /// </summary>
-    public void TryAddSolution(EntityUid uid, SmokeComponent component, Solution solution)
+    private void TryAddSolution(EntityUid uid, Solution solution)
     {
         if (solution.Volume == FixedPoint2.Zero)
             return;
@@ -237,4 +332,14 @@ public sealed class SmokeSystem : EntitySystem
 
         UpdateVisuals(uid);
     }
+
+    private void UpdateVisuals(EntityUid uid)
+    {
+        if (!TryComp(uid, out AppearanceComponent? appearance) ||
+            !_solutionSystem.TryGetSolution(uid, SmokeComponent.SolutionName, out var solution))
+            return;
+
+        var color = solution.GetColor(_prototype);
+        _appearance.SetData(uid, SmokeVisuals.Color, color, appearance);
+    }
 }
index 79ebc520c800fb44b17cb339c70c64686a2aea94..afb3a363265268ee8f589070f3b7cf7719ba44d4 100644 (file)
@@ -12,7 +12,7 @@ public sealed partial class VentClogRuleComponent : Component
     /// Somewhat safe chemicals to put in foam that probably won't instantly kill you.
     /// There is a small chance of using any reagent, ignoring this.
     /// </summary>
-    [DataField("safeishVentChemicals", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
+    [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
     public IReadOnlyList<string> SafeishVentChemicals = new[]
     {
         "Water", "Blood", "Slime", "SpaceDrugs", "SpaceCleaner", "Nutriment", "Sugar", "SpaceLube", "Ephedrine", "Ale", "Beer", "SpaceGlue"
@@ -21,31 +21,31 @@ public sealed partial class VentClogRuleComponent : Component
     /// <summary>
     /// Sound played when foam is being created.
     /// </summary>
-    [DataField("sound")]
+    [DataField]
     public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/extinguish.ogg");
 
     /// <summary>
-    /// The standard reagent quantity to put in the foam, modfied by event severity.
+    /// The standard reagent quantity to put in the foam, modified by event severity.
     /// </summary>
-    [DataField("reagentQuantity"), ViewVariables(VVAccess.ReadWrite)]
-    public int ReagentQuantity = 200;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int ReagentQuantity = 100;
 
     /// <summary>
-    /// The standard spreading of the foam, not modfied by event severity.
+    /// The standard spreading of the foam, not modified by event severity.
     /// </summary>
-    [DataField("spread"), ViewVariables(VVAccess.ReadWrite)]
-    public int Spread = 20;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int Spread = 16;
 
     /// <summary>
     /// How long the foam lasts for
     /// </summary>
-    [DataField("time"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
     public float Time = 20f;
 
     /// <summary>
     /// Reagents that gets the weak numbers used instead of regular ones.
     /// </summary>
-    [DataField("weakReagents", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
+    [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
     public IReadOnlyList<string> WeakReagents = new[]
     {
         "SpaceLube", "SpaceGlue"
@@ -54,12 +54,12 @@ public sealed partial class VentClogRuleComponent : Component
     /// <summary>
     /// Quantity of weak reagents to put in the foam.
     /// </summary>
-    [DataField("weakReagentQuantity"), ViewVariables(VVAccess.ReadWrite)]
-    public int WeakReagentQuantity = 60;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int WeakReagentQuantity = 50;
 
     /// <summary>
     /// Spread of the foam for weak reagents.
     /// </summary>
-    [DataField("weakSpread"), ViewVariables(VVAccess.ReadWrite)]
-    public int WeakSpread = 2;
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int WeakSpread = 3;
 }
index 5ef28304e7e3b77d6eb6b78d80d0a070812c9e71..f378aec3fb9a6771045f30aa085e6c0973dc6612 100644 (file)
@@ -3,10 +3,8 @@ using Content.Server.Station.Components;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Reagent;
 using JetBrains.Annotations;
-using Robust.Shared.Audio;
 using Robust.Shared.Random;
 using System.Linq;
-using Content.Server.Chemistry.Components;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.GameTicking.Rules.Components;
 using Content.Server.StationEvents.Components;
@@ -53,9 +51,8 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
             solution.AddReagent(reagent, quantity);
 
             var foamEnt = Spawn("Foam", transform.Coordinates);
-            var smoke = EnsureComp<SmokeComponent>(foamEnt);
-            smoke.SpreadAmount = weak ? component.WeakSpread : component.Spread;
-            _smoke.Start(foamEnt, smoke, solution, component.Time);
+            var spreadAmount = weak ? component.WeakSpread : component.Spread;
+            _smoke.StartSmoke(foamEnt, solution, component.Time, spreadAmount);
             Audio.PlayPvs(component.Sound, transform.Coordinates);
         }
     }
index 93d9d5a01b571106fb0ad5efbdaac27bc0716417..2d2c230b12e1f35370ddb80fb3f5d1d5b3d0be0e 100644 (file)
@@ -1,5 +1,4 @@
 using System.Linq;
-using Content.Server.Chemistry.Components;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
 using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
@@ -38,8 +37,7 @@ public sealed class FoamArtifactSystem : EntitySystem
         var range = (int) MathF.Round(MathHelper.Lerp(component.MinFoamAmount, component.MaxFoamAmount, _random.NextFloat(0, 1f)));
         sol.AddReagent(component.SelectedReagent, component.ReagentAmount);
         var foamEnt = Spawn("Foam", xform.Coordinates);
-        var smoke = EnsureComp<SmokeComponent>(foamEnt);
-        smoke.SpreadAmount = range * 4;
-        _smoke.Start(foamEnt, smoke, sol, component.Duration);
+        var spreadAmount = range * 4;
+        _smoke.StartSmoke(foamEnt, sol, component.Duration, spreadAmount);
     }
 }
diff --git a/Content.Shared/Chemistry/Components/SmokeAffectedComponent.cs b/Content.Shared/Chemistry/Components/SmokeAffectedComponent.cs
new file mode 100644 (file)
index 0000000..def6940
--- /dev/null
@@ -0,0 +1,24 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Chemistry.Components;
+
+/// <summary>
+/// This is used for entities which are currently being affected by smoke.
+/// Manages the gradual metabolism every second.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class SmokeAffectedComponent : Component
+{
+    /// <summary>
+    /// The time at which the next smoke metabolism will occur.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan NextSecond;
+
+    /// <summary>
+    /// The smoke that is currently affecting this entity.
+    /// </summary>
+    [DataField]
+    public EntityUid SmokeEntity;
+}
diff --git a/Content.Shared/Chemistry/Components/SmokeComponent.cs b/Content.Shared/Chemistry/Components/SmokeComponent.cs
new file mode 100644 (file)
index 0000000..9d88fca
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.FixedPoint;
+using Content.Shared.Fluids.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Chemistry.Components;
+
+/// <summary>
+/// Stores solution on an anchored entity that has touch and ingestion reactions
+/// to entities that collide with it. Similar to <see cref="PuddleComponent"/>
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SmokeComponent : Component
+{
+    public const string SolutionName = "solutionArea";
+
+    /// <summary>
+    /// The max amount of tiles this smoke cloud can spread to.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int SpreadAmount;
+
+    /// <summary>
+    /// The max rate at which chemicals are transferred from the smoke to the person inhaling it.
+    /// Calculated as (total volume of chemicals in smoke) / (<see cref="Duration"/>)
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public FixedPoint2 TransferRate;
+
+    /// <summary>
+    /// The total lifespan of the smoke.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+    public float Duration = 10;
+}