diff --git a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs index bc91846bd9..d0d7995d2d 100644 --- a/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/PuddleTest.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Coordinates; using Content.Shared.FixedPoint; @@ -23,6 +24,8 @@ namespace Content.IntegrationTests.Tests.Fluids await server.WaitIdleAsync(); var mapManager = server.ResolveDependency(); + var entitySystemManager = server.ResolveDependency(); + var spillSystem = entitySystemManager.GetEntitySystem(); server.Assert(() => { @@ -30,7 +33,7 @@ namespace Content.IntegrationTests.Tests.Fluids var grid = GetMainGrid(mapManager); var (x, y) = GetMainTile(grid).GridIndices; var coordinates = new EntityCoordinates(grid.GridEntityId, x, y); - var puddle = solution.SpillAt(coordinates, "PuddleSmear"); + var puddle = spillSystem.SpillAt(solution, coordinates, "PuddleSmear"); Assert.NotNull(puddle); }); @@ -46,6 +49,8 @@ namespace Content.IntegrationTests.Tests.Fluids await server.WaitIdleAsync(); var mapManager = server.ResolveDependency(); + var entitySystemManager = server.ResolveDependency(); + var spillSystem = entitySystemManager.GetEntitySystem(); IMapGrid grid = null; @@ -66,7 +71,7 @@ namespace Content.IntegrationTests.Tests.Fluids { var coordinates = grid.ToCoordinates(); var solution = new Solution("Water", FixedPoint2.New(20)); - var puddle = solution.SpillAt(coordinates, "PuddleSmear"); + var puddle = spillSystem.SpillAt(solution, coordinates, "PuddleSmear"); Assert.Null(puddle); }); @@ -120,13 +125,17 @@ namespace Content.IntegrationTests.Tests.Fluids float evaporateTime = default; PuddleComponent puddle = null; EvaporationComponent evaporation; + var amount = 2; + + var entitySystemManager = server.ResolveDependency(); + var spillSystem = entitySystemManager.GetEntitySystem(); // Spawn a puddle await server.WaitAssertion(() => { var solution = new Solution("Water", FixedPoint2.New(amount)); - puddle = solution.SpillAt(sCoordinates, "PuddleSmear"); + puddle = spillSystem.SpillAt(solution, sCoordinates, "PuddleSmear"); // Check that the puddle was created Assert.NotNull(puddle); @@ -184,4 +193,4 @@ namespace Content.IntegrationTests.Tests.Fluids }); } } -} +} \ No newline at end of file diff --git a/Content.Server/Atmos/Reactions/WaterVaporReaction.cs b/Content.Server/Atmos/Reactions/WaterVaporReaction.cs index 6d489ad584..ba30ef4ec8 100644 --- a/Content.Server/Atmos/Reactions/WaterVaporReaction.cs +++ b/Content.Server/Atmos/Reactions/WaterVaporReaction.cs @@ -1,10 +1,12 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Content.Shared.Maps; using JetBrains.Annotations; +using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Atmos.Reactions @@ -35,8 +37,9 @@ namespace Content.Server.Atmos.Reactions // Remove the moles from the mixture... mixture.AdjustMoles(GasId, -MolesPerUnit); - var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex); - tileRef.SpillAt(new Solution(Reagent, FixedPoint2.New(MolesPerUnit)), PuddlePrototype, sound: false); + var tileRef = tile.GridIndices.GetTileRef(tile.GridIndex); + EntitySystem.Get() + .SpillAt(tileRef, new Solution(Reagent, FixedPoint2.New(MolesPerUnit)), PuddlePrototype, sound: false); return ReactionResult.Reacting; } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs index ed4dca054e..74c594637f 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using System.Text; using Content.Server.Chemistry.Components.SolutionManager; +using Content.Server.Database; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; @@ -128,5 +130,27 @@ namespace Content.Server.Chemistry.EntitySystems return true; } + + public static string ToPrettyString(Solution solution) + { + var sb = new StringBuilder(); + sb.Append("Solution content: ["); + var first = true; + foreach (var (id, quantity) in solution.Contents) + { + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.AppendFormat("{0}: {1}u", id, quantity); + + } + sb.Append(']'); + return sb.ToString(); + } } } diff --git a/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs b/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs index 19fe6ff906..f0a1bdc3a5 100644 --- a/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/SpillIfPuddlePresentTileReaction.cs @@ -1,9 +1,11 @@ using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using JetBrains.Annotations; +using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Serialization.Manager.Attributes; @@ -15,9 +17,12 @@ namespace Content.Server.Chemistry.TileReactions { public FixedPoint2 TileReact(TileRef tile, ReagentPrototype reagent, FixedPoint2 reactVolume) { - if (reactVolume < 5 || !tile.TryGetPuddle(null, out _)) return FixedPoint2.Zero; + var spillSystem = EntitySystem.Get(); + if (reactVolume < 5 || !spillSystem.TryGetPuddle(tile, out _)) return FixedPoint2.Zero; - return tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", true, false, true) != null ? reactVolume : FixedPoint2.Zero; + return spillSystem.SpillAt(tile,new Solution(reagent.ID, reactVolume), "PuddleSmear", true, false, true) != null + ? reactVolume + : FixedPoint2.Zero; } } } diff --git a/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs b/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs index 208d6cb5a6..b561de1b68 100644 --- a/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs +++ b/Content.Server/Chemistry/TileReactions/SpillTileReaction.cs @@ -1,10 +1,12 @@ using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Content.Shared.Slippery; using JetBrains.Annotations; +using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Serialization.Manager.Attributes; @@ -24,7 +26,8 @@ namespace Content.Server.Chemistry.TileReactions if (reactVolume < 5) return FixedPoint2.Zero; // TODO Make this not puddle smear. - var puddle = tile.SpillAt(new Solution(reagent.ID, reactVolume), "PuddleSmear", _overflow, false, true); + var puddle = EntitySystem.Get() + .SpillAt(tile, new Solution(reagent.ID, reactVolume), "PuddleSmear", _overflow, false, true); if (puddle != null) { diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SpillBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SpillBehavior.cs index a91435657c..0697ab1e60 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SpillBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SpillBehavior.cs @@ -1,5 +1,6 @@ using Content.Server.Chemistry.EntitySystems; using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; @@ -20,10 +21,10 @@ namespace Content.Server.Destructible.Thresholds.Behaviors /// /// Entity on which behavior is executed /// system calling the behavior - /// public void Execute(EntityUid owner, DestructibleSystem system) { var solutionContainerSystem = EntitySystem.Get(); + var spillableSystem = EntitySystem.Get(); var coordinates = system.EntityManager.GetComponent(owner).Coordinates; @@ -31,12 +32,12 @@ namespace Content.Server.Destructible.Thresholds.Behaviors solutionContainerSystem.TryGetSolution(owner, spillableComponent.SolutionName, out var compSolution)) { - compSolution.SpillAt(coordinates, "PuddleSmear", false); + spillableSystem.SpillAt(compSolution, coordinates, "PuddleSmear", false); } else if (Solution != null && solutionContainerSystem.TryGetSolution(owner, Solution, out var behaviorSolution)) { - behaviorSolution.SpillAt(coordinates, "PuddleSmear", false); + spillableSystem.SpillAt(behaviorSolution, coordinates, "PuddleSmear", false); } } } diff --git a/Content.Server/Fluids/Components/MopComponent.cs b/Content.Server/Fluids/Components/MopComponent.cs index 508e1a42d8..b13cd385c3 100644 --- a/Content.Server/Fluids/Components/MopComponent.cs +++ b/Content.Server/Fluids/Components/MopComponent.cs @@ -88,6 +88,7 @@ namespace Content.Server.Fluids.Components * will spill some of the mop's solution onto the puddle which will evaporate eventually. */ var solutionSystem = EntitySystem.Get(); + var spillableSystem = EntitySystem.Get(); if (!solutionSystem.TryGetSolution(Owner.Uid, SolutionName, out var contents ) || Mopping || @@ -105,8 +106,8 @@ namespace Content.Server.Fluids.Components if (eventArgs.Target == null) { // Drop the liquid on the mop on to the ground - solutionSystem.SplitSolution(Owner.Uid, contents, FixedPoint2.Min(ResidueAmount, CurrentVolume)) - .SpillAt(eventArgs.ClickLocation, "PuddleSmear"); + var solution = solutionSystem.SplitSolution(Owner.Uid, contents, FixedPoint2.Min(ResidueAmount, CurrentVolume)); + spillableSystem.SpillAt(solution, eventArgs.ClickLocation, "PuddleSmear"); return true; } @@ -146,9 +147,9 @@ namespace Content.Server.Fluids.Components // After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly. // we do this WITHOUT adding to the existing puddle. Otherwise we have might have water puddles with the vomit sprite. - solutionSystem.SplitSolution(Owner.Uid, contents, transferAmount) - .SplitSolution(ResidueAmount) - .SpillAt(eventArgs.ClickLocation, "PuddleSmear", combine: false); + var splitSolution = solutionSystem.SplitSolution(Owner.Uid, contents, transferAmount) + .SplitSolution(ResidueAmount); + spillableSystem.SpillAt(splitSolution, eventArgs.ClickLocation, "PuddleSmear", combine: false); } else { diff --git a/Content.Server/Fluids/Components/SpillExtensions.cs b/Content.Server/Fluids/Components/SpillExtensions.cs deleted file mode 100644 index 120b875e8d..0000000000 --- a/Content.Server/Fluids/Components/SpillExtensions.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Coordinates.Helpers; -using Content.Server.Fluids.EntitySystems; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; - -namespace Content.Server.Fluids.Components -{ - // TODO: Kill these with fire - public static class SpillExtensions - { - /// - /// Spills the specified solution at the entity's location if possible. - /// - /// - /// The entity to use as a location to spill the solution at. - /// - /// Initial solution for the prototype. - /// The prototype to use. - /// Play the spill sound. - /// The puddle if one was created, null otherwise. - /// Whether to attempt to merge with existing puddles - public static PuddleComponent? SpillAt(this Solution solution, IEntity entity, string prototype, - bool sound = true, bool combine = true) - { - return solution.SpillAt(entity.Transform.Coordinates, prototype, sound, combine: combine); - } - - /// - /// Spills the specified solution at the entity's location if possible. - /// - /// - /// The entity to use as a location to spill the solution at. - /// - /// Initial solution for the prototype. - /// The prototype to use. - /// Play the spill sound. - /// - /// Whether to attempt to merge with existing puddles - /// The puddle if one was created, null otherwise. - public static PuddleComponent? SpillAt(this Solution solution, EntityUid entity, string prototype, - bool sound = true, IEntityManager? entityManager = null, bool combine = true) - { - entityManager ??= IoCManager.Resolve(); - - return solution.SpillAt(entityManager.GetComponent(entity).Coordinates, prototype, sound, combine: combine); - } - - /// - /// Spills the specified solution at the entity's location if possible. - /// - /// - /// The entity to use as a location to spill the solution at. - /// - /// Initial solution for the prototype. - /// The prototype to use. - /// The puddle if one was created, null otherwise. - /// Play the spill sound. - /// Whether to attempt to merge with existing puddles - /// True if a puddle was created, false otherwise. - public static bool TrySpillAt(this Solution solution, IEntity entity, string prototype, - [NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true, bool combine = true) - { - puddle = solution.SpillAt(entity, prototype, sound, combine: combine); - return puddle != null; - } - - /// - /// Spills solution at the specified grid coordinates. - /// - /// Initial solution for the prototype. - /// The coordinates to spill the solution at. - /// The prototype to use. - /// Whether or not to play the spill sound. - /// Whether to attempt to merge with existing puddles - /// The puddle if one was created, null otherwise. - public static PuddleComponent? SpillAt(this Solution solution, EntityCoordinates coordinates, string prototype, - bool overflow = true, bool sound = true, bool combine = true) - { - if (solution.TotalVolume == 0) return null; - - var mapManager = IoCManager.Resolve(); - var entityManager = IoCManager.Resolve(); - - if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var mapGrid)) - return null; // Let's not spill to space. - - return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound, combine: combine); - } - - /// - /// Spills the specified solution at the entity's location if possible. - /// - /// The coordinates to spill the solution at. - /// Initial solution for the prototype. - /// The prototype to use. - /// The puddle if one was created, null otherwise. - /// Play the spill sound. - /// Whether to attempt to merge with existing puddles - /// True if a puddle was created, false otherwise. - public static bool TrySpillAt(this Solution solution, EntityCoordinates coordinates, string prototype, - [NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true, bool combine = true) - { - puddle = solution.SpillAt(coordinates, prototype, sound, combine: combine); - return puddle != null; - } - - public static bool TryGetPuddle(this TileRef tileRef, GridTileLookupSystem? gridTileLookupSystem, - [NotNullWhen(true)] out PuddleComponent? puddle) - { - foreach (var entity in tileRef.GetEntitiesInTileFast(gridTileLookupSystem)) - { - if (entity.TryGetComponent(out PuddleComponent? p)) - { - puddle = p; - return true; - } - } - - puddle = null; - return false; - } - - public static PuddleComponent? SpillAt(this TileRef tileRef, Solution solution, string prototype, - bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true) - { - if (solution.TotalVolume <= 0) return null; - - // If space return early, let that spill go out into the void - if (tileRef.Tile.IsEmpty) return null; - - var mapManager = IoCManager.Resolve(); - var prototypeManager = IoCManager.Resolve(); - var serverEntityManager = IoCManager.Resolve(); - - var gridId = tileRef.GridIndex; - if (!mapManager.TryGetGrid(gridId, out var mapGrid)) return null; // Let's not spill to invalid grids. - - if (!noTileReact) - { - // First, do all tile reactions - foreach (var reagent in solution.Contents.ToArray()) - { - var proto = prototypeManager.Index(reagent.ReagentId); - proto.ReactionTile(tileRef, reagent.Quantity); - } - } - - // Tile reactions used up everything. - if (solution.CurrentVolume == FixedPoint2.Zero) - return null; - - // Get normalized co-ordinate for spill location and spill it in the centre - // TODO: Does SnapGrid or something else already do this? - var spillGridCoords = mapGrid.GridTileToWorld(tileRef.GridIndices); - - var spillEntities = IoCManager.Resolve() - .GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray(); - foreach (var spillEntity in spillEntities) - { - if (EntitySystem.Get() - .TryGetRefillableSolution(spillEntity.Uid, out var solutionContainerComponent)) - { - EntitySystem.Get().Refill(spillEntity.Uid, solutionContainerComponent, - solution.SplitSolution(FixedPoint2.Min( - solutionContainerComponent.AvailableVolume, - solutionContainerComponent.MaxSpillRefill)) - ); - } - } - - var puddleSystem = EntitySystem.Get(); - - if (combine) - { - foreach (var spillEntity in spillEntities) - { - if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent)) continue; - - if (!overflow && puddleSystem.WouldOverflow(puddleComponent.Owner.Uid, solution, puddleComponent)) return null; - - if (!puddleSystem.TryAddSolution(puddleComponent.Owner.Uid, solution, sound)) continue; - - return puddleComponent; - } - } - - var puddleEnt = serverEntityManager.SpawnEntity(prototype, spillGridCoords); - var newPuddleComponent = puddleEnt.GetComponent(); - - puddleSystem.TryAddSolution(newPuddleComponent.Owner.Uid, solution, sound); - - return newPuddleComponent; - } - } -} diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index e432b10adb..011c17b031 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -40,7 +40,6 @@ namespace Content.Server.Fluids.EntitySystems base.Initialize(); SubscribeLocalEvent(OnUnanchored); - SubscribeLocalEvent(AddSpillVerb); SubscribeLocalEvent(HandlePuddleExamined); SubscribeLocalEvent(OnUpdate); SubscribeLocalEvent(OnInit); @@ -90,25 +89,7 @@ namespace Content.Server.Fluids.EntitySystems } } - private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution)) - return; - - if (solution.DrainAvailable == FixedPoint2.Zero) - return; - - Verb verb = new(); - verb.Text = Loc.GetString("spill-target-verb-get-data-text"); - // TODO VERB ICONS spill icon? pouring out a glass/beaker? - verb.Act = () => _solutionContainerSystem.SplitSolution(args.Target.Uid, - solution, solution.DrainAvailable).SpillAt(args.Target.Transform.Coordinates, "PuddleSmear"); - verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately. - args.Verbs.Add(verb); - } private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args) { diff --git a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs index a780346bb4..b7d9e935af 100644 --- a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs @@ -1,12 +1,22 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Administration.Logs; using Content.Server.Chemistry.EntitySystems; -using Content.Server.Construction.Components; +using Content.Server.Coordinates.Helpers; using Content.Server.Fluids.Components; -using Content.Shared.Examine; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Database; +using Content.Shared.FixedPoint; using Content.Shared.Throwing; using Content.Shared.Verbs; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; namespace Content.Server.Fluids.EntitySystems; @@ -14,20 +24,178 @@ namespace Content.Server.Fluids.EntitySystems; public class SpillableSystem : EntitySystem { [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly PuddleSystem _puddleSystem = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityLookup _entityLookup = default!; + [Dependency] private readonly GridTileLookupSystem _gridTileLookupSystem = default!; + [Dependency] private readonly AdminLogSystem _logSystem = default!; - public override void Initialize() + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(SpillOnLand); + SubscribeLocalEvent(AddSpillVerb); } - void SpillOnLand(EntityUid uid, SpillableComponent component, LandEvent args) { - if (args.User != null && _solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solutionComponent)) + /// + /// Spills the specified solution at the entity's location if possible. + /// + /// + /// The entity to use as a location to spill the solution at. + /// + /// Initial solution for the prototype. + /// The prototype to use. + /// Play the spill sound. + /// Whether to attempt to merge with existing puddles + /// Optional Transform component + /// The puddle if one was created, null otherwise. + public PuddleComponent? SpillAt(EntityUid uid, Solution solution, string prototype, + bool sound = true, bool combine = true, TransformComponent? transformComponent = null) + { + return !Resolve(uid, ref transformComponent, false) + ? null + : SpillAt(solution, transformComponent.Coordinates, prototype, sound: sound, combine: combine); + } + + private void SpillOnLand(EntityUid uid, SpillableComponent component, LandEvent args) + { + if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution)) return; + + if (args.User != null) { - _solutionContainerSystem - .Drain(uid, solutionComponent, solutionComponent.DrainAvailable) - .SpillAt(EntityManager.GetComponent(uid).Coordinates, "PuddleSmear"); + _logSystem.Add(LogType.Landed, + $"{EntityManager.ToPrettyString(uid)} spilled {SolutionContainerSystem.ToPrettyString(solution)} on landing"); } + + var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable); + SpillAt(drainedSolution, EntityManager.GetComponent(uid).Coordinates, "PuddleSmear"); } + private void AddSpillVerb(EntityUid uid, SpillableComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!_solutionContainerSystem.TryGetDrainableSolution(args.Target.Uid, out var solution)) + return; + + if (solution.DrainAvailable == FixedPoint2.Zero) + return; + + Verb verb = new(); + verb.Text = Loc.GetString("spill-target-verb-get-data-text"); + // TODO VERB ICONS spill icon? pouring out a glass/beaker? + verb.Act = () => + { + var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target.Uid, + solution, solution.DrainAvailable); + SpillAt(puddleSolution, args.Target.Transform.Coordinates, "PuddleSmear"); + }; + verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately. + args.Verbs.Add(verb); + } + + /// + /// Spills solution at the specified grid coordinates. + /// + /// Initial solution for the prototype. + /// The coordinates to spill the solution at. + /// The prototype to use. + /// If the puddle overflow will be calculated. Defaults to true. + /// Whether or not to play the spill sound. + /// Whether to attempt to merge with existing puddles + /// The puddle if one was created, null otherwise. + public PuddleComponent? SpillAt(Solution solution, EntityCoordinates coordinates, string prototype, + bool overflow = true, bool sound = true, bool combine = true) + { + if (solution.TotalVolume == 0) return null; + + + if (!_mapManager.TryGetGrid(coordinates.GetGridId(EntityManager), out var mapGrid)) + return null; // Let's not spill to space. + + return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound, + combine: combine); + } + + public bool TryGetPuddle(TileRef tileRef, [NotNullWhen(true)] out PuddleComponent? puddle) + { + foreach (var entity in tileRef.GetEntitiesInTileFast(_gridTileLookupSystem)) + { + if (entity.TryGetComponent(out PuddleComponent? p)) + { + puddle = p; + return true; + } + } + + puddle = null; + return false; + } + + public PuddleComponent? SpillAt(TileRef tileRef, Solution solution, string prototype, + bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true) + { + if (solution.TotalVolume <= 0) return null; + + // If space return early, let that spill go out into the void + if (tileRef.Tile.IsEmpty) return null; + + var gridId = tileRef.GridIndex; + if (!_mapManager.TryGetGrid(gridId, out var mapGrid)) return null; // Let's not spill to invalid grids. + + if (!noTileReact) + { + // First, do all tile reactions + foreach (var (reagentId, quantity) in solution.Contents) + { + var proto = _prototypeManager.Index(reagentId); + proto.ReactionTile(tileRef, quantity); + } + } + + // Tile reactions used up everything. + if (solution.CurrentVolume == FixedPoint2.Zero) + return null; + + // Get normalized co-ordinate for spill location and spill it in the centre + // TODO: Does SnapGrid or something else already do this? + var spillGridCoords = mapGrid.GridTileToWorld(tileRef.GridIndices); + + var spillEntities = _entityLookup.GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray(); + foreach (var spillEntity in spillEntities) + { + if (_solutionContainerSystem.TryGetRefillableSolution(spillEntity.Uid, out var solutionContainerComponent)) + { + _solutionContainerSystem.Refill(spillEntity.Uid, solutionContainerComponent, + solution.SplitSolution(FixedPoint2.Min( + solutionContainerComponent.AvailableVolume, + solutionContainerComponent.MaxSpillRefill)) + ); + } + } + + if (combine) + { + foreach (var spillEntity in spillEntities) + { + if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent)) continue; + + if (!overflow && _puddleSystem.WouldOverflow(puddleComponent.Owner.Uid, solution, puddleComponent)) + return null; + + if (!_puddleSystem.TryAddSolution(puddleComponent.Owner.Uid, solution, sound)) continue; + + return puddleComponent; + } + } + + var puddleEnt = EntityManager.SpawnEntity(prototype, spillGridCoords); + var newPuddleComponent = puddleEnt.GetComponent(); + + _puddleSystem.TryAddSolution(newPuddleComponent.Owner.Uid, solution, sound); + + return newPuddleComponent; + } } diff --git a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs index e7ef1023c2..ff0d702838 100644 --- a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chemistry.EntitySystems; using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Audio; @@ -20,6 +21,7 @@ namespace Content.Server.Nutrition.EntitySystems public class CreamPieSystem : SharedCreamPieSystem { [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; + [Dependency] private readonly SpillableSystem _spillableSystem = default!; protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie) { @@ -27,7 +29,7 @@ namespace Content.Server.Nutrition.EntitySystems if (creamPie.Owner.TryGetComponent(out var foodComp) && _solutionsSystem.TryGetSolution(creamPie.Owner.Uid, foodComp.SolutionName, out var solution)) { - solution.SpillAt(creamPie.Owner, "PuddleSmear", false); + _spillableSystem.SpillAt(creamPie.OwnerUid, solution, "PuddleSmear", false); } } diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 2eb49ef1a1..b327c8c1a2 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Server.Fluids.Components; +using Content.Server.Fluids.EntitySystems; using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.ActionBlocker; @@ -43,6 +44,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedAdminLogSystem _logSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly SpillableSystem _spillableSystem = default!; public override void Initialize() { @@ -188,7 +190,7 @@ namespace Content.Server.Nutrition.EntitySystems var entity = EntityManager.GetEntity(uid); var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.DrainAvailable); - solution.SpillAt(entity, "PuddleSmear"); + _spillableSystem.SpillAt(entity.Uid, solution, "PuddleSmear"); SoundSystem.Play(Filter.Pvs(entity), component.BurstSound.GetSound(), entity, AudioParams.Default.WithVolume(-4)); } @@ -279,7 +281,7 @@ namespace Content.Server.Nutrition.EntitySystems if (EntityManager.HasComponent(uid)) { - drain.SpillAt(userUid, "PuddleSmear"); + _spillableSystem.SpillAt(userUid, drain, "PuddleSmear"); return true; } @@ -375,7 +377,7 @@ namespace Content.Server.Nutrition.EntitySystems _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-cannot-drink-other"), uid, Filter.Entities(args.User)); - drained.SpillAt(uid, "PuddleSmear"); + _spillableSystem.SpillAt(uid, drained, "PuddleSmear"); return; } @@ -388,7 +390,7 @@ namespace Content.Server.Nutrition.EntitySystems _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), uid, Filter.Entities(args.User)); - drained.SpillAt(uid, "PuddleSmear"); + _spillableSystem.SpillAt(uid, drained, "PuddleSmear"); return; }