diff --git a/Content.Server/Chemistry/Components/VaporComponent.cs b/Content.Server/Chemistry/Components/VaporComponent.cs
index a2f4a01a2a..1bc3881b1d 100644
--- a/Content.Server/Chemistry/Components/VaporComponent.cs
+++ b/Content.Server/Chemistry/Components/VaporComponent.cs
@@ -1,4 +1,4 @@
-using Content.Shared.FixedPoint;
+using Robust.Shared.Map;
namespace Content.Server.Chemistry.Components
{
@@ -7,11 +7,31 @@ namespace Content.Server.Chemistry.Components
{
public const string SolutionName = "vapor";
- [DataField("transferAmount")]
- public FixedPoint2 TransferAmount = FixedPoint2.New(0.5);
+ ///
+ /// Stores data on the previously reacted tile. We only want to do reaction checks once per tile.
+ ///
+ [DataField]
+ public TileRef? PreviousTileRef;
- public float ReactTimer;
- [DataField("active")]
+ ///
+ /// Percentage of the reagent that is reacted with the TileReaction.
+ ///
+ /// 0.5 = 50% of the reagent is reacted.
+ ///
+ ///
+ [DataField]
+ public float TransferAmountPercentage;
+
+ ///
+ /// The minimum amount of the reagent that will be reacted with the TileReaction.
+ /// We do this to prevent floating point issues. A reagent with a low percentage transfer amount will
+ /// transfer 0.01~ forever and never get deleted.
+ /// Defaults to 0.05 if not defined, a good general value.
+ ///
+ [DataField]
+ public float MinimumTransferAmount = 0.05f;
+
+ [DataField]
public bool Active;
}
}
diff --git a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs
index c9b64e649e..55489e0b31 100644
--- a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs
@@ -30,8 +30,6 @@ namespace Content.Server.Chemistry.EntitySystems
[Dependency] private readonly ReactiveSystem _reactive = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
- private const float ReactTime = 0.125f;
-
public override void Initialize()
{
base.Initialize();
@@ -50,13 +48,19 @@ namespace Content.Server.Chemistry.EntitySystems
}
// Check for collision with a impassable object (e.g. wall) and stop
- if ((args.OtherFixture.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
+ if ((args.OtherFixture.CollisionLayer & (int)CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
{
EntityManager.QueueDeleteEntity(entity);
}
}
- public void Start(Entity vapor, TransformComponent vaporXform, Vector2 dir, float speed, MapCoordinates target, float aliveTime, EntityUid? user = null)
+ public void Start(Entity vapor,
+ TransformComponent vaporXform,
+ Vector2 dir,
+ float speed,
+ MapCoordinates target,
+ float aliveTime,
+ EntityUid? user = null)
{
vapor.Comp.Active = true;
var despawn = EnsureComp(vapor);
@@ -83,7 +87,9 @@ namespace Content.Server.Chemistry.EntitySystems
return false;
}
- if (!_solutionContainerSystem.TryGetSolution(vapor.Owner, VaporComponent.SolutionName, out var vaporSolution))
+ if (!_solutionContainerSystem.TryGetSolution(vapor.Owner,
+ VaporComponent.SolutionName,
+ out var vaporSolution))
{
return false;
}
@@ -93,53 +99,71 @@ namespace Content.Server.Chemistry.EntitySystems
public override void Update(float frameTime)
{
+ base.Update(frameTime);
+
+ // Enumerate over all VaporComponents
var query = EntityQueryEnumerator();
while (query.MoveNext(out var uid, out var vaporComp, out var container, out var xform))
{
- foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
+ // Return early if we're not active
+ if (!vaporComp.Active)
+ continue;
+
+ // Get the current location of the vapor entity first
+ if (TryComp(xform.GridUid, out MapGridComponent? gridComp))
{
- Update(frameTime, (uid, vaporComp), soln, xform);
- }
- }
- }
+ var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
- private void Update(float frameTime, Entity ent, Entity soln, TransformComponent xform)
- {
- var (entity, vapor) = ent;
- if (!vapor.Active)
- return;
+ // Check if the tile is a tile we've reacted with previously. If so, skip it.
+ // If we have no previous tile reference, we don't return so we can save one.
+ if (vaporComp.PreviousTileRef != null && tile == vaporComp.PreviousTileRef)
+ continue;
- vapor.ReactTimer += frameTime;
-
- var contents = soln.Comp.Solution;
- if (vapor.ReactTimer >= ReactTime && TryComp(xform.GridUid, out MapGridComponent? gridComp))
- {
- vapor.ReactTimer = 0;
-
- var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
- foreach (var reagentQuantity in contents.Contents.ToArray())
- {
- if (reagentQuantity.Quantity == FixedPoint2.Zero) continue;
- var reagent = _protoManager.Index(reagentQuantity.Reagent.Prototype);
-
- var reaction =
- reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f, EntityManager, reagentQuantity.Reagent.Data);
-
- if (reaction > reagentQuantity.Quantity)
+ // Enumerate over all the reagents in the vapor entity solution
+ foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
{
- Log.Error($"Tried to tile react more than we have for reagent {reagentQuantity}. Found {reaction} and we only have {reagentQuantity.Quantity}");
- reaction = reagentQuantity.Quantity;
+ // Iterate over the reagents in the solution
+ // Reason: Each reagent in our solution may have a unique TileReaction
+ // In this instance, we check individually for each reagent's TileReaction
+ // This is not doing chemical reactions!
+ var contents = soln.Comp.Solution;
+ foreach (var reagentQuantity in contents.Contents.ToArray())
+ {
+ // Check if the reagent is empty
+ if (reagentQuantity.Quantity == FixedPoint2.Zero)
+ continue;
+
+ var reagent = _protoManager.Index(reagentQuantity.Reagent.Prototype);
+
+ // Limit the reaction amount to a minimum value to ensure no floating point funnies.
+ // Ex: A solution with a low percentage transfer amount will slowly approach 0.01... and never get deleted
+ var clampedAmount = Math.Max(
+ (float)reagentQuantity.Quantity * vaporComp.TransferAmountPercentage,
+ vaporComp.MinimumTransferAmount);
+
+ // Preform the reagent's TileReaction
+ var reaction =
+ reagent.ReactionTile(tile,
+ clampedAmount,
+ EntityManager,
+ reagentQuantity.Reagent.Data);
+
+ if (reaction > reagentQuantity.Quantity)
+ reaction = reagentQuantity.Quantity;
+
+ _solutionContainerSystem.RemoveReagent(soln, reagentQuantity.Reagent, reaction);
+ }
+
+ // Delete the vapor entity if it has no contents
+ if (contents.Volume == 0)
+ EntityManager.QueueDeleteEntity(uid);
+
}
- _solutionContainerSystem.RemoveReagent(soln, reagentQuantity.Reagent, reaction);
+ // Set the previous tile reference to the current tile
+ vaporComp.PreviousTileRef = tile;
}
}
-
- if (contents.Volume == 0)
- {
- // Delete this
- EntityManager.QueueDeleteEntity(entity);
- }
}
}
}
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
index 2f91f0c0f8..e8a5ff22bb 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml
@@ -99,8 +99,8 @@
- type: Tag
tags:
- Spray
-# Vapor
+# Vapor
- type: entity
id: Vapor
name: "vapor"
@@ -111,6 +111,8 @@
vapor:
maxVol: 50
- type: Vapor
+ active: true
+ transferAmountPercentage: 0.5
- type: AnimationPlayer
- type: Sprite
sprite: Effects/chempuff.rsi
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
index 4d7ef02f8a..31767ba8b3 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
@@ -936,6 +936,7 @@
- BulletImpassable
- type: Vapor
active: true
+ transferAmountPercentage: 1
- type: Appearance
- type: VaporVisuals