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 }