diff --git a/Content.Client/Fluids/PuddleSystem.cs b/Content.Client/Fluids/PuddleSystem.cs
index 54b1d5b86b931a..5dbffe0fd2f13a 100644
--- a/Content.Client/Fluids/PuddleSystem.cs
+++ b/Content.Client/Fluids/PuddleSystem.cs
@@ -1,7 +1,9 @@
using Content.Client.IconSmoothing;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Fluids;
using Content.Shared.Fluids.Components;
using Robust.Client.GameObjects;
+using Robust.Shared.Map;
namespace Content.Client.Fluids;
@@ -21,7 +23,7 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
if (args.Sprite == null)
return;
- float volume = 1f;
+ var volume = 1f;
if (args.AppearanceData.TryGetValue(PuddleVisuals.CurrentVolume, out var volumeObj))
{
@@ -64,4 +66,38 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
args.Sprite.Color *= baseColor;
}
}
+
+ #region Spill
+
+ // Maybe someday we'll have clientside prediction for entity spawning, but not today.
+ // Until then, these methods do nothing on the client.
+ ///
+ public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true, TransformComponent? transformComponent = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true, bool tileReact = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ #endregion Spill
}
diff --git a/Content.Server/Fluids/Components/PreventSpillerComponent.cs b/Content.Server/Fluids/Components/PreventSpillerComponent.cs
deleted file mode 100644
index 37096f1bb3ca79..00000000000000
--- a/Content.Server/Fluids/Components/PreventSpillerComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Fluids.Components;
-
-[RegisterComponent]
-public sealed partial class PreventSpillerComponent : Component
-{
-
-}
diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs
index efaca271d3bb6d..ce5b5b363782ba 100644
--- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs
+++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs
@@ -1,5 +1,4 @@
using Content.Server.Chemistry.Containers.EntitySystems;
-using Content.Server.Fluids.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
@@ -8,7 +7,6 @@
using Content.Shared.Clothing.Components;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Database;
-using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.IdentityManagement;
@@ -16,7 +14,6 @@
using Content.Shared.Popups;
using Content.Shared.Spillable;
using Content.Shared.Throwing;
-using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Player;
@@ -24,9 +21,6 @@ namespace Content.Server.Fluids.EntitySystems;
public sealed partial class PuddleSystem
{
- [Dependency] private readonly OpenableSystem _openable = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
-
protected override void InitializeSpillable()
{
base.InitializeSpillable();
@@ -34,7 +28,6 @@ protected override void InitializeSpillable()
SubscribeLocalEvent(SpillOnLand);
// Openable handles the event if it's closed
SubscribeLocalEvent(SplashOnMeleeHit, after: [typeof(OpenableSystem)]);
- SubscribeLocalEvent>(AddSpillVerb);
SubscribeLocalEvent(OnGotEquipped);
SubscribeLocalEvent(OnOverflow);
SubscribeLocalEvent(OnDoAfter);
@@ -134,7 +127,7 @@ private void SpillOnLand(Entity entity, ref LandEvent args)
if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution))
return;
- if (_openable.IsClosed(entity.Owner))
+ if (Openable.IsClosed(entity.Owner))
return;
if (args.User != null)
@@ -153,7 +146,7 @@ private void SpillOnLand(Entity entity, ref LandEvent args)
private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args)
{
// Don’t care about closed containers.
- if (_openable.IsClosed(ent))
+ if (Openable.IsClosed(ent))
return;
// Don’t care about empty containers.
@@ -163,57 +156,6 @@ private void OnAttemptPacifiedThrow(Entity ent, ref AttemptP
args.Cancel("pacified-cannot-throw-spill");
}
- private void AddSpillVerb(Entity entity, ref GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- if (!_solutionContainerSystem.TryGetSolution(args.Target, entity.Comp.SolutionName, out var soln, out var solution))
- return;
-
- if (_openable.IsClosed(args.Target))
- return;
-
- if (solution.Volume == FixedPoint2.Zero)
- return;
-
- if (_entityManager.HasComponent(args.User))
- return;
-
-
- Verb verb = new()
- {
- Text = Loc.GetString("spill-target-verb-get-data-text")
- };
-
- // TODO VERB ICONS spill icon? pouring out a glass/beaker?
- if (entity.Comp.SpillDelay == null)
- {
- var target = args.Target;
- verb.Act = () =>
- {
- var puddleSolution = _solutionContainerSystem.SplitSolution(soln.Value, solution.Volume);
- TrySpillAt(Transform(target).Coordinates, puddleSolution, out _);
- };
- }
- else
- {
- var user = args.User;
- verb.Act = () =>
- {
- _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, entity.Comp.SpillDelay ?? 0, new SpillDoAfterEvent(), entity.Owner, target: entity.Owner)
- {
- BreakOnDamage = true,
- BreakOnMove = true,
- NeedHand = true,
- });
- };
- }
- verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
- verb.DoContactInteraction = true;
- args.Verbs.Add(verb);
- }
-
private void OnDoAfter(Entity entity, ref SpillDoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs
index e3481f98da3b7f..923210cc73c2c4 100644
--- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs
+++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs
@@ -46,7 +46,6 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
[Dependency] private readonly AudioSystem _audio = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly ReactiveSystem _reactive = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -551,11 +550,8 @@ public Solution GetOverflowSolution(EntityUid uid, PuddleComponent? puddle = nul
#region Spill
- ///
- /// First splashes reagent on reactive entities near the spilling entity, then spills the rest regularly to a
- /// puddle. This is intended for 'destructive' spills, like when entities are destroyed or thrown.
- ///
- public bool TrySplashSpillAt(EntityUid uid,
+ ///
+ public override bool TrySplashSpillAt(EntityUid uid,
EntityCoordinates coordinates,
Solution solution,
out EntityUid puddleUid,
@@ -600,11 +596,8 @@ public bool TrySplashSpillAt(EntityUid uid,
return TrySpillAt(coordinates, solution, out puddleUid, sound);
}
- ///
- /// Spills solution at the specified coordinates.
- /// Will add to an existing puddle if present or create a new one if not.
- ///
- public bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true)
+ ///
+ public override bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true)
{
if (solution.Volume == 0)
{
@@ -622,10 +615,8 @@ public bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out Ent
return TrySpillAt(_map.GetTileRef(gridUid.Value, mapGrid, coordinates), solution, out puddleUid, sound);
}
- ///
- ///
- ///
- public bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true,
+ ///
+ public override bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true,
TransformComponent? transformComponent = null)
{
if (!Resolve(uid, ref transformComponent, false))
@@ -637,10 +628,8 @@ public bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid
return TrySpillAt(transformComponent.Coordinates, solution, out puddleUid, sound: sound);
}
- ///
- ///
- ///
- public bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true,
+ ///
+ public override bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true,
bool tileReact = true)
{
if (solution.Volume <= 0)
diff --git a/Content.Shared/Fluids/Components/PreventSpillerComponent.cs b/Content.Shared/Fluids/Components/PreventSpillerComponent.cs
new file mode 100644
index 00000000000000..e396d9faf52eb9
--- /dev/null
+++ b/Content.Shared/Fluids/Components/PreventSpillerComponent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Fluids.Components;
+
+///
+/// Blocks this entity's ability to spill solution containing entities via the verb menu.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class PreventSpillerComponent : Component
+{
+
+}
diff --git a/Content.Shared/Fluids/Components/SpillableComponent.cs b/Content.Shared/Fluids/Components/SpillableComponent.cs
index a1b5fa17eb8c6f..428d91f2de75f9 100644
--- a/Content.Shared/Fluids/Components/SpillableComponent.cs
+++ b/Content.Shared/Fluids/Components/SpillableComponent.cs
@@ -2,6 +2,12 @@
namespace Content.Shared.Fluids.Components;
+///
+/// Makes a solution contained in this entity spillable.
+/// Spills can occur when a container with this component overflows,
+/// is used to melee attack something, is equipped (see ),
+/// lands after being thrown, or has the Spill verb used.
+///
[RegisterComponent]
public sealed partial class SpillableComponent : Component
{
diff --git a/Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs b/Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs
index 77730e5afc51ce..1e9e742a38fada 100644
--- a/Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs
+++ b/Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs
@@ -1,14 +1,23 @@
+using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.Examine;
+using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Spillable;
+using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
namespace Content.Shared.Fluids;
public abstract partial class SharedPuddleSystem
{
+ [Dependency] protected readonly SharedOpenableSystem Openable = default!;
+
protected virtual void InitializeSpillable()
{
SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent>(AddSpillVerb);
}
private void OnExamined(Entity entity, ref ExaminedEvent args)
@@ -21,4 +30,55 @@ private void OnExamined(Entity entity, ref ExaminedEvent arg
args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
}
}
+
+ private void AddSpillVerb(Entity entity, ref GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract)
+ return;
+
+ if (!_solutionContainerSystem.TryGetSolution(args.Target, entity.Comp.SolutionName, out var soln, out var solution))
+ return;
+
+ if (Openable.IsClosed(args.Target))
+ return;
+
+ if (solution.Volume == FixedPoint2.Zero)
+ return;
+
+ if (HasComp(args.User))
+ return;
+
+
+ Verb verb = new()
+ {
+ Text = Loc.GetString("spill-target-verb-get-data-text")
+ };
+
+ // TODO VERB ICONS spill icon? pouring out a glass/beaker?
+ if (entity.Comp.SpillDelay == null)
+ {
+ var target = args.Target;
+ verb.Act = () =>
+ {
+ var puddleSolution = _solutionContainerSystem.SplitSolution(soln.Value, solution.Volume);
+ TrySpillAt(Transform(target).Coordinates, puddleSolution, out _);
+ };
+ }
+ else
+ {
+ var user = args.User;
+ verb.Act = () =>
+ {
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, entity.Comp.SpillDelay ?? 0, new SpillDoAfterEvent(), entity.Owner, target: entity.Owner)
+ {
+ BreakOnDamage = true,
+ BreakOnMove = true,
+ NeedHand = true,
+ });
+ };
+ }
+ verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
+ verb.DoContactInteraction = true;
+ args.Verbs.Add(verb);
+ }
}
diff --git a/Content.Shared/Fluids/SharedPuddleSystem.cs b/Content.Shared/Fluids/SharedPuddleSystem.cs
index e4bd61baa8ebb1..f573c042c5521e 100644
--- a/Content.Shared/Fluids/SharedPuddleSystem.cs
+++ b/Content.Shared/Fluids/SharedPuddleSystem.cs
@@ -1,12 +1,14 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
+using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.Movement.Events;
using Content.Shared.StepTrigger.Components;
+using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Shared.Fluids;
@@ -15,6 +17,7 @@ public abstract partial class SharedPuddleSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
///
/// The lowest threshold to be considered for puddle sprite states as well as slipperiness of a puddle.
@@ -106,4 +109,54 @@ private void HandlePuddleExamined(Entity entity, ref ExaminedEv
args.PushMarkup(Loc.GetString("puddle-component-examine-evaporating-no"));
}
}
+
+ #region Spill
+ // 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
+ // client and replacing or merging them with the ones spawned by the server when the client goes to
+ // replicate those, and I am not enough of a wizard to attempt implementing that.
+
+ ///
+ /// First splashes reagent on reactive entities near the spilling entity, then spills the rest regularly to a
+ /// puddle. This is intended for 'destructive' spills, like when entities are destroyed or thrown.
+ ///
+ ///
+ /// On the client, this will always set to and return false.
+ ///
+ public abstract bool TrySplashSpillAt(EntityUid uid,
+ EntityCoordinates coordinates,
+ Solution solution,
+ out EntityUid puddleUid,
+ bool sound = true,
+ EntityUid? user = null);
+
+ ///
+ /// Spills solution at the specified coordinates.
+ /// Will add to an existing puddle if present or create a new one if not.
+ ///
+ ///
+ /// On the client, this will always set to and return false.
+ ///
+ public abstract bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true);
+
+ ///
+ ///
+ ///
+ ///
+ /// On the client, this will always set to and return false.
+ ///
+ public abstract bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true,
+ TransformComponent? transformComponent = null);
+
+ ///
+ ///
+ ///
+ ///
+ /// On the client, this will always set to and return false.
+ ///
+ public abstract bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true,
+ bool tileReact = true);
+
+ #endregion Spill
}