diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index cc0b65137a..f4d82118fa 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -1,13 +1,14 @@ using System.Linq; using Content.Server.Body.Components; using Content.Server.Chemistry.EntitySystems; +using Content.Server.Chemistry.ReactionEffects; using Content.Server.Fluids.EntitySystems; using Content.Server.HealthExaminable; using Content.Server.Popups; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reaction; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; -using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.MobState.Components; using Robust.Shared.Audio; @@ -38,6 +39,34 @@ public sealed class BloodstreamSystem : EntitySystem SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnHealthBeingExamined); SubscribeLocalEvent(OnBeingGibbed); + SubscribeLocalEvent(OnReactionAttempt); + } + + private void OnReactionAttempt(EntityUid uid, BloodstreamComponent component, ReactionAttemptEvent args) + { + if (args.Solution.Name != BloodstreamComponent.DefaultBloodSolutionName + && args.Solution.Name != BloodstreamComponent.DefaultChemicalsSolutionName + && args.Solution.Name != BloodstreamComponent.DefaultBloodTemporarySolutionName) + return; + + foreach (var effect in args.Reaction.Effects) + { + switch (effect) + { + case CreateEntityReactionEffect: // Prevent entities from spawning in the bloodstream + case AreaReactionEffect: // No spontaneous smoke or foam leaking out of blood vessels. + args.Cancel(); + return; + } + } + + // The area-reaction effect canceling is part of avoiding smoke-fork-bombs (create two smoke bombs, that when + // ingested by mobs create more smoke). This also used to act as a rapid chemical-purge, because all the + // reagents would get carried away by the smoke/foam. This does still work for the stomach (I guess people vomit + // up the smoke or spawned entities?). + + // TODO apply organ damage instead of just blocking the reaction? + // Having cheese-clots form in your veins can't be good for you. } public override void Update(float frameTime) diff --git a/Content.Server/Chemistry/EntitySystems/SolutionAreaEffectSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionAreaEffectSystem.cs index 2c16787623..0d5d12018d 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionAreaEffectSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionAreaEffectSystem.cs @@ -1,5 +1,7 @@ using System.Linq; using Content.Server.Chemistry.Components; +using Content.Server.Chemistry.ReactionEffects; +using Content.Shared.Chemistry.Reaction; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -8,6 +10,13 @@ namespace Content.Server.Chemistry.EntitySystems [UsedImplicitly] public sealed class SolutionAreaEffectSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnReactionAttempt); + } + public override void Update(float frameTime) { foreach (var inception in EntityManager.EntityQuery().ToArray()) @@ -15,5 +24,21 @@ namespace Content.Server.Chemistry.EntitySystems inception.InceptionUpdate(frameTime); } } + + private void OnReactionAttempt(EntityUid uid, SolutionAreaEffectComponent component, ReactionAttemptEvent args) + { + if (args.Solution.Name != SolutionAreaEffectComponent.SolutionName) + return; + + // Prevent smoke/foam fork bombs (smoke creating more smoke). + foreach (var effect in args.Reaction.Effects) + { + if (effect is AreaReactionEffect) + { + args.Cancel(); + return; + } + } + } } } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs index 5ce2b05b68..a40f0f2560 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs @@ -131,7 +131,10 @@ public sealed partial class SolutionContainerSystem public static string ToPrettyString(Solution solution) { var sb = new StringBuilder(); - sb.Append("["); + if (solution.Name == null) + sb.Append("["); + else + sb.Append($"{solution.Name}:["); var first = true; foreach (var (id, quantity) in solution.Contents) { diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs index 788f7895dc..bf0e577ea5 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -41,9 +41,9 @@ public sealed partial class SolutionContainerSystem : EntitySystem private void InitSolution(EntityUid uid, SolutionContainerManagerComponent component, ComponentInit args) { - foreach (var keyValue in component.Solutions) + foreach (var (name, solutionHolder) in component.Solutions) { - var solutionHolder = keyValue.Value; + solutionHolder.Name = name; if (solutionHolder.MaxVolume == FixedPoint2.Zero) { solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume @@ -283,7 +283,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem if (!solutionsMgr.Solutions.ContainsKey(name)) { - var newSolution = new Solution(); + var newSolution = new Solution() { Name = name }; solutionsMgr.Solutions.Add(name, newSolution); } diff --git a/Content.Shared/Chemistry/Components/Solution.cs b/Content.Shared/Chemistry/Components/Solution.cs index 696c297669..30fdf23e2c 100644 --- a/Content.Shared/Chemistry/Components/Solution.cs +++ b/Content.Shared/Chemistry/Components/Solution.cs @@ -44,6 +44,11 @@ namespace Content.Shared.Chemistry.Components public Color Color => GetColor(); + /// + /// The name of this solution, if it is contained in some + /// + public string? Name; + /// /// Constructs an empty solution (ex. an empty beaker). /// diff --git a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs index 4b2dc7ecca..279e832c41 100644 --- a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs +++ b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs @@ -107,7 +107,7 @@ namespace Content.Shared.Chemistry.Reaction /// The reaction to check. /// How many times this reaction can occur. /// - private static bool CanReact(Solution solution, ReactionPrototype reaction, out FixedPoint2 lowestUnitReactions) + private bool CanReact(Solution solution, ReactionPrototype reaction, EntityUid owner, out FixedPoint2 lowestUnitReactions) { lowestUnitReactions = FixedPoint2.MaxValue; if (solution.Temperature < reaction.MinimumTemperature) @@ -120,6 +120,14 @@ namespace Content.Shared.Chemistry.Reaction return false; } + var attempt = new ReactionAttemptEvent(reaction, solution); + RaiseLocalEvent(owner, attempt, false); + if (attempt.Cancelled) + { + lowestUnitReactions = FixedPoint2.Zero; + return false; + } + foreach (var reactantData in reaction.Reactants) { var reactantName = reactantData.Key; @@ -220,7 +228,7 @@ namespace Content.Shared.Chemistry.Reaction // attempt to perform any applicable reaction foreach (var reaction in reactions) { - if (!CanReact(solution, reaction, out var unitReactions)) + if (!CanReact(solution, reaction, owner, out var unitReactions)) { toRemove.Add(reaction); continue; @@ -288,4 +296,22 @@ namespace Content.Shared.Chemistry.Reaction Logger.Error($"{nameof(Solution)} {owner} could not finish reacting in under {MaxReactionIterations} loops."); } } + + /// + /// Raised directed at the owner of a solution to determine whether the reaction should be allowed to occur. + /// + /// + /// Some solution containers (e.g., bloodstream, smoke, foam) use this to block certain reactions from occurring. + /// + public sealed class ReactionAttemptEvent : CancellableEntityEventArgs + { + public readonly ReactionPrototype Reaction; + public readonly Solution Solution; + + public ReactionAttemptEvent(ReactionPrototype reaction, Solution solution) + { + Reaction = reaction; + Solution = solution; + } + } }