fix solution contents duplication on spill behavior (#33231)

* I’M SCREAMING INTO THE VOID AND IT’S NOT LISTENING

* review

* explodes pancakes with mind

* graaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

* Meteors RAAAAAAAAAAH

* I'm so tired of solutions

* whhop

* revert

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Milon
2025-12-22 08:59:30 +01:00
committed by GitHub
parent eb41d5010b
commit f59ef4b986
6 changed files with 93 additions and 62 deletions

View File

@@ -73,7 +73,19 @@ public sealed class PuddleSystem : SharedPuddleSystem
// Maybe someday we'll have clientside prediction for entity spawning, but not today.
// Until then, these methods do nothing on the client.
/// <inheritdoc/>
public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
public override bool TrySplashSpillAt(Entity<SpillableComponent?> entity, EntityCoordinates coordinates, out EntityUid puddleUid, out Solution solution, bool sound = true, EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
solution = new Solution();
return false;
}
public override bool TrySplashSpillAt(EntityUid entity,
EntityCoordinates coordinates,
Solution spilled,
out EntityUid puddleUid,
bool sound = true,
EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
return false;

View File

@@ -1,42 +1,38 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Shared.Fluids.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
namespace Content.Server.Destructible.Thresholds.Behaviors
namespace Content.Server.Destructible.Thresholds.Behaviors;
[UsedImplicitly]
[DataDefinition]
public sealed partial class SpillBehavior : IThresholdBehavior
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class SpillBehavior : IThresholdBehavior
/// <summary>
/// Optional fallback solution name if SpillableComponent is not present.
/// </summary>
[DataField]
public string? Solution;
/// <summary>
/// When triggered, spills the entity's solution onto the ground.
/// Will first try to use the solution from a SpillableComponent if present,
/// otherwise falls back to the solution specified in the behavior's data fields.
/// The solution is properly drained/split before spilling to prevent double-spilling with other behaviors.
/// </summary>
/// <param name="owner">Entity whose solution will be spilled</param>
/// <param name="system">System calling this behavior</param>
/// <param name="cause">Optional entity that caused this behavior to trigger</param>
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
[DataField]
public string? Solution;
var puddleSystem = system.EntityManager.System<PuddleSystem>();
var solutionContainer = system.EntityManager.System<SharedSolutionContainerSystem>();
var coordinates = system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates;
/// <summary>
/// If there is a SpillableComponent on EntityUidowner use it to create a puddle/smear.
/// Or whatever solution is specified in the behavior itself.
/// If none are available do nothing.
/// </summary>
/// <param name="owner">Entity on which behavior is executed</param>
/// <param name="system">system calling the behavior</param>
/// <param name="cause"></param>
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
var solutionContainerSystem = system.EntityManager.System<SharedSolutionContainerSystem>();
var spillableSystem = system.EntityManager.System<PuddleSystem>();
var coordinates = system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates;
if (system.EntityManager.TryGetComponent(owner, out SpillableComponent? spillableComponent) &&
solutionContainerSystem.TryGetSolution(owner, spillableComponent.SolutionName, out _, out var compSolution))
{
spillableSystem.TrySplashSpillAt(owner, coordinates, compSolution, out _, false, user: cause);
}
else if (Solution != null &&
solutionContainerSystem.TryGetSolution(owner, Solution, out _, out var behaviorSolution))
{
spillableSystem.TrySplashSpillAt(owner, coordinates, behaviorSolution, out _, user: cause);
}
}
// Spill the solution that was drained/split
if (solutionContainer.TryGetSolution(owner, Solution, out _, out var solution))
puddleSystem.TrySplashSpillAt(owner, coordinates, solution, out _, false, cause);
else
puddleSystem.TrySplashSpillAt(owner, coordinates, out _, out _, false, cause);
}
}

View File

@@ -29,23 +29,14 @@ public sealed partial class PuddleSystem
private void SpillOnLand(Entity<SpillableComponent> entity, ref LandEvent args)
{
if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution))
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
return;
if (Openable.IsClosed(entity.Owner))
return;
if (!entity.Comp.SpillWhenThrown)
return;
if (args.User != null)
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
{
AdminLogger.Add(LogType.Landed,
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
}
var drainedSolution = _solutionContainerSystem.Drain(entity.Owner, soln.Value, solution.Volume);
TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, drainedSolution, out _);
}
private void OnDoAfter(Entity<SpillableComponent> entity, ref SpillDoAfterEvent args)

View File

@@ -369,16 +369,37 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
// TODO: This can be predicted once https://github.com/space-wizards/RobustToolbox/pull/5849 is merged
/// <inheritdoc/>
public override bool TrySplashSpillAt(EntityUid uid,
public override bool TrySplashSpillAt(Entity<SpillableComponent?> entity,
EntityCoordinates coordinates,
Solution solution,
out EntityUid puddleUid,
out Solution spilled,
bool sound = true,
EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
spilled = new Solution();
if (!Resolve(entity, ref entity.Comp))
return false;
if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var solution))
return false;
spilled = solution.Value.Comp.Solution;
return TrySplashSpillAt(entity, coordinates, spilled, out puddleUid, sound, user);
}
public override bool TrySplashSpillAt(EntityUid entity,
EntityCoordinates coordinates,
Solution spilled,
out EntityUid puddleUid,
bool sound = true,
EntityUid? user = null)
{
puddleUid = EntityUid.Invalid;
if (solution.Volume == 0)
if (spilled.Volume == 0)
return false;
var targets = new List<EntityUid>();
@@ -392,26 +413,28 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
var owner = ent.Owner;
// between 5 and 30%
var splitAmount = solution.Volume * _random.NextFloat(0.05f, 0.30f);
var splitSolution = solution.SplitSolution(splitAmount);
var splitAmount = spilled.Volume * _random.NextFloat(0.05f, 0.30f);
var splitSolution = spilled.SplitSolution(splitAmount);
if (user != null)
{
AdminLogger.Add(LogType.Landed,
$"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):entity} which splashed a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} onto {ToPrettyString(owner):target}");
$"{ToPrettyString(user.Value):user} threw {ToPrettyString(entity):entity} which splashed a solution {SharedSolutionContainerSystem.ToPrettyString(spilled):solution} onto {ToPrettyString(owner):target}");
}
targets.Add(owner);
Reactive.DoEntityReaction(owner, splitSolution, ReactionMethod.Touch);
Popups.PopupEntity(
Loc.GetString("spill-land-spilled-on-other", ("spillable", uid),
("target", Identity.Entity(owner, EntityManager))), owner, PopupType.SmallCaution);
Popups.PopupEntity(Loc.GetString("spill-land-spilled-on-other",
("spillable", entity),
("target", Identity.Entity(owner, EntityManager))),
owner,
PopupType.SmallCaution);
}
_color.RaiseEffect(solution.GetColor(_prototypeManager), targets,
Filter.Pvs(uid, entityManager: EntityManager));
_color.RaiseEffect(spilled.GetColor(_prototypeManager), targets,
Filter.Pvs(entity, entityManager: EntityManager));
return TrySpillAt(coordinates, solution, out puddleUid, sound);
return TrySpillAt(coordinates, spilled, out puddleUid, sound);
}
/// <inheritdoc/>

View File

@@ -354,6 +354,15 @@ public abstract partial class SharedPuddleSystem : EntitySystem
}
#region Spill
/// <inheritdoc cref="TrySplashSpillAt(EntityUid,EntityCoordinates,Solution,out EntityUid,bool,EntityUid?)"/>
public abstract bool TrySplashSpillAt(Entity<SpillableComponent?> entity,
EntityCoordinates coordinates,
out EntityUid puddleUid,
out Solution spilled,
bool sound = true,
EntityUid? user = null);
// These methods are in Shared to make it easier to interact with PuddleSystem in Shared code.
// Note that they always fail when run on the client, not creating a puddle and returning false.
// Adding proper prediction to this system would require spawning temporary puddle entities on the
@@ -367,9 +376,9 @@ public abstract partial class SharedPuddleSystem : EntitySystem
/// <remarks>
/// On the client, this will always set <paramref name="puddleUid"/> to <see cref="EntityUid.Invalid"/> and return false.
/// </remarks>
public abstract bool TrySplashSpillAt(EntityUid uid,
public abstract bool TrySplashSpillAt(EntityUid entity,
EntityCoordinates coordinates,
Solution solution,
Solution spilled,
out EntityUid puddleUid,
bool sound = true,
EntityUid? user = null);

View File

@@ -179,8 +179,8 @@ public sealed partial class PressurizedSolutionSystem : EntitySystem
var solution = _solutionContainer.SplitSolution(soln.Value, interactions.Volume);
// Spray the solution onto the ground and anyone nearby
if (TryComp(entity, out TransformComponent? transform))
_puddle.TrySplashSpillAt(entity, transform.Coordinates, solution, out _, sound: false);
var coordinates = Transform(entity).Coordinates;
_puddle.TrySplashSpillAt(entity.Owner, coordinates, out _, out _, sound: false);
var drinkName = Identity.Entity(entity, EntityManager);