Fix smoke fork bomb (#7401)

This commit is contained in:
Leon Friedrich
2022-04-05 04:02:33 +12:00
committed by GitHub
parent 39c81f4a50
commit 66b0820ed6
6 changed files with 95 additions and 7 deletions

View File

@@ -1,13 +1,14 @@
using System.Linq; using System.Linq;
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Chemistry.ReactionEffects;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.HealthExaminable; using Content.Server.HealthExaminable;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.Examine;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.MobState.Components; using Content.Shared.MobState.Components;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -38,6 +39,34 @@ public sealed class BloodstreamSystem : EntitySystem
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged); SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined); SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed); SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
SubscribeLocalEvent<BloodstreamComponent, ReactionAttemptEvent>(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) public override void Update(float frameTime)

View File

@@ -1,5 +1,7 @@
using System.Linq; using System.Linq;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.ReactionEffects;
using Content.Shared.Chemistry.Reaction;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -8,6 +10,13 @@ namespace Content.Server.Chemistry.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public sealed class SolutionAreaEffectSystem : EntitySystem public sealed class SolutionAreaEffectSystem : EntitySystem
{ {
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionAreaEffectComponent, ReactionAttemptEvent>(OnReactionAttempt);
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
foreach (var inception in EntityManager.EntityQuery<SolutionAreaEffectInceptionComponent>().ToArray()) foreach (var inception in EntityManager.EntityQuery<SolutionAreaEffectInceptionComponent>().ToArray())
@@ -15,5 +24,21 @@ namespace Content.Server.Chemistry.EntitySystems
inception.InceptionUpdate(frameTime); 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;
}
}
}
} }
} }

View File

@@ -131,7 +131,10 @@ public sealed partial class SolutionContainerSystem
public static string ToPrettyString(Solution solution) public static string ToPrettyString(Solution solution)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("["); if (solution.Name == null)
sb.Append("[");
else
sb.Append($"{solution.Name}:[");
var first = true; var first = true;
foreach (var (id, quantity) in solution.Contents) foreach (var (id, quantity) in solution.Contents)
{ {

View File

@@ -41,9 +41,9 @@ public sealed partial class SolutionContainerSystem : EntitySystem
private void InitSolution(EntityUid uid, SolutionContainerManagerComponent component, ComponentInit args) 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) if (solutionHolder.MaxVolume == FixedPoint2.Zero)
{ {
solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume
@@ -283,7 +283,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem
if (!solutionsMgr.Solutions.ContainsKey(name)) if (!solutionsMgr.Solutions.ContainsKey(name))
{ {
var newSolution = new Solution(); var newSolution = new Solution() { Name = name };
solutionsMgr.Solutions.Add(name, newSolution); solutionsMgr.Solutions.Add(name, newSolution);
} }

View File

@@ -44,6 +44,11 @@ namespace Content.Shared.Chemistry.Components
public Color Color => GetColor(); public Color Color => GetColor();
/// <summary>
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
/// </summary>
public string? Name;
/// <summary> /// <summary>
/// Constructs an empty solution (ex. an empty beaker). /// Constructs an empty solution (ex. an empty beaker).
/// </summary> /// </summary>

View File

@@ -107,7 +107,7 @@ namespace Content.Shared.Chemistry.Reaction
/// <param name="reaction">The reaction to check.</param> /// <param name="reaction">The reaction to check.</param>
/// <param name="lowestUnitReactions">How many times this reaction can occur.</param> /// <param name="lowestUnitReactions">How many times this reaction can occur.</param>
/// <returns></returns> /// <returns></returns>
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; lowestUnitReactions = FixedPoint2.MaxValue;
if (solution.Temperature < reaction.MinimumTemperature) if (solution.Temperature < reaction.MinimumTemperature)
@@ -120,6 +120,14 @@ namespace Content.Shared.Chemistry.Reaction
return false; 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) foreach (var reactantData in reaction.Reactants)
{ {
var reactantName = reactantData.Key; var reactantName = reactantData.Key;
@@ -220,7 +228,7 @@ namespace Content.Shared.Chemistry.Reaction
// attempt to perform any applicable reaction // attempt to perform any applicable reaction
foreach (var reaction in reactions) foreach (var reaction in reactions)
{ {
if (!CanReact(solution, reaction, out var unitReactions)) if (!CanReact(solution, reaction, owner, out var unitReactions))
{ {
toRemove.Add(reaction); toRemove.Add(reaction);
continue; continue;
@@ -288,4 +296,22 @@ namespace Content.Shared.Chemistry.Reaction
Logger.Error($"{nameof(Solution)} {owner} could not finish reacting in under {MaxReactionIterations} loops."); Logger.Error($"{nameof(Solution)} {owner} could not finish reacting in under {MaxReactionIterations} loops.");
} }
} }
/// <summary>
/// Raised directed at the owner of a solution to determine whether the reaction should be allowed to occur.
/// </summary>
/// <reamrks>
/// Some solution containers (e.g., bloodstream, smoke, foam) use this to block certain reactions from occurring.
/// </reamrks>
public sealed class ReactionAttemptEvent : CancellableEntityEventArgs
{
public readonly ReactionPrototype Reaction;
public readonly Solution Solution;
public ReactionAttemptEvent(ReactionPrototype reaction, Solution solution)
{
Reaction = reaction;
Solution = solution;
}
}
} }