Files
tbd-station-14/Content.Server/Chemistry/EntitySystems/VaporSystem.cs
ArtisticRoomba 44925b9be7 Fix spray nozzle not cleaning reagents properly (#35950)
* init, god help us all

* further refining

* final round of bugfixes

* whoopsies

* To file scoped namespace

* first review

* oopsie

* oopsie woopsie

* pie is on my face

* persistence

* datafieldn't

* make PreviousTileRef nullable

* change component to file scoped namespace

* Minor tweaks:

- We clamp the reaction amount to a minimum value because when working with percentages and dividing, we approach numbers like 0.01 and never actually properly delete the entity (because we check for zero). This allows us to react with a minimum amount and cleans things up nicely.
- Minor clarification to comments.
- Rebalancing of the spray nozzle projectile.

* the scug lies!!!!

* undo file scoped namespace in system

* kid named warning

---------

Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
2025-04-19 22:41:24 -04:00

170 lines
7.3 KiB
C#

using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Physics;
using Content.Shared.Throwing;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Spawners;
using System.Numerics;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
internal sealed class VaporSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly ReactiveSystem _reactive = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VaporComponent, StartCollideEvent>(HandleCollide);
}
private void HandleCollide(Entity<VaporComponent> entity, ref StartCollideEvent args)
{
if (!EntityManager.TryGetComponent(entity.Owner, out SolutionContainerManagerComponent? contents)) return;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((entity.Owner, contents)))
{
var solution = soln.Comp.Solution;
_reactive.DoEntityReaction(args.OtherEntity, solution, ReactionMethod.Touch);
}
// Check for collision with a impassable object (e.g. wall) and stop
if ((args.OtherFixture.CollisionLayer & (int)CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
{
EntityManager.QueueDeleteEntity(entity);
}
}
public void Start(Entity<VaporComponent> vapor,
TransformComponent vaporXform,
Vector2 dir,
float speed,
MapCoordinates target,
float aliveTime,
EntityUid? user = null)
{
vapor.Comp.Active = true;
var despawn = EnsureComp<TimedDespawnComponent>(vapor);
despawn.Lifetime = aliveTime;
// Set Move
if (EntityManager.TryGetComponent(vapor, out PhysicsComponent? physics))
{
_physics.SetLinearDamping(vapor, physics, 0f);
_physics.SetAngularDamping(vapor, physics, 0f);
_throwing.TryThrow(vapor, dir, speed, user: user);
var distance = (target.Position - _transformSystem.GetWorldPosition(vaporXform)).Length();
var time = (distance / physics.LinearVelocity.Length());
despawn.Lifetime = MathF.Min(aliveTime, time);
}
}
internal bool TryAddSolution(Entity<VaporComponent> vapor, Solution solution)
{
if (solution.Volume == 0)
{
return false;
}
if (!_solutionContainerSystem.TryGetSolution(vapor.Owner,
VaporComponent.SolutionName,
out var vaporSolution))
{
return false;
}
return _solutionContainerSystem.TryAddSolution(vaporSolution.Value, solution);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// Enumerate over all VaporComponents
var query = EntityQueryEnumerator<VaporComponent, SolutionContainerManagerComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var vaporComp, out var container, out var xform))
{
// 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))
{
var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
// 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;
// Enumerate over all the reagents in the vapor entity solution
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
{
// 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<ReagentPrototype>(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);
}
// Set the previous tile reference to the current tile
vaporComp.PreviousTileRef = tile;
}
}
}
}
}