From dbd9ee1671652a5515022235acba0cfbd1e5e404 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Thu, 2 Jul 2020 20:31:40 +0200 Subject: [PATCH] Fix reagent spill errors (#1180) Co-authored-by: ComicIronic --- .../Components/Fluids/PuddleComponent.cs | 160 +++++++++++------- Content.Shared/Chemistry/ReagentUnit.cs | 15 ++ .../Entities/Items/Consumables/drinks.yml | 1 + .../Items/Consumables/drinks_bottles.yml | 1 + .../Items/Consumables/drinks_cans.yml | 1 + .../Items/Consumables/drinks_cups.yml | 1 + .../kitchen_reagent_containers.yml | 1 + .../Items/Consumables/trash_drinks.yml | 1 + .../Prototypes/Entities/Items/chemistry.yml | 4 + Resources/Prototypes/Entities/janitor.yml | 2 + 10 files changed, 128 insertions(+), 59 deletions(-) diff --git a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs index f927117413..7e5356fc3b 100644 --- a/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using Content.Server.GameObjects.Components.Chemistry; using Content.Shared.Chemistry; @@ -11,6 +12,7 @@ using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Maths; @@ -40,15 +42,23 @@ namespace Content.Server.GameObjects.Components.Fluids // based on behaviour (e.g. someone being punched vs slashed with a sword would have different blood sprite) // to check for low volumes for evaporation or whatever +#pragma warning disable 649 + [Dependency] private readonly IMapManager _mapManager; +#pragma warning restore 649 + public override string Name => "Puddle"; private CancellationTokenSource _evaporationToken; private ReagentUnit _evaporateThreshold; // How few we can hold prior to self-destructing private float _evaporateTime; private string _spillSound; - private DateTime _lastOverflow = DateTime.Now; - private SpriteComponent _spriteComponent; + /// + /// Whether or not this puddle is currently overflowing onto its neighbors + /// + private bool _overflown; + + private SpriteComponent _spriteComponent; private SnapGridComponent _snapGrid; public ReagentUnit MaxVolume @@ -65,6 +75,7 @@ namespace Content.Server.GameObjects.Components.Fluids public ReagentUnit OverflowVolume => _overflowVolume; [ViewVariables] private ReagentUnit _overflowVolume; + private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume; private SolutionComponent _contents; private int _spriteVariants; @@ -113,7 +124,7 @@ namespace Content.Server.GameObjects.Components.Fluids } // Flow rate should probably be controlled globally so this is it for now - internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true) + internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true, bool checkForOverflow = true) { if (solution.TotalVolume == 0) { @@ -126,7 +137,12 @@ namespace Content.Server.GameObjects.Components.Fluids } UpdateStatus(); - CheckOverflow(); + + if (checkForOverflow) + { + CheckOverflow(); + } + if (checkForEvaporate) { CheckEvaporate(); @@ -198,7 +214,6 @@ namespace Content.Server.GameObjects.Components.Fluids _spriteComponent.Color = newColor; _spriteComponent.Dirty(); - } /// @@ -206,80 +221,52 @@ namespace Content.Server.GameObjects.Components.Fluids /// private void CheckOverflow() { - if (CurrentVolume <= _overflowVolume) + if (CurrentVolume <= _overflowVolume || _overflown) { return; } - // Essentially: - // Spill at least 1 solution to each neighbor (so most of the time each puddle is getting 1 max) - // If there's no puddle at the neighbor then add one. + var nextPuddles = new List() {this}; + var overflownPuddles = new List(); - // Setup - // If there's more neighbors to spill to then there are reagents to go around (coz integers) - var overflowAmount = CurrentVolume - OverflowVolume; - - var neighborPuddles = new List(8); - - // Will overflow to each neighbor; if it already has a puddle entity then add to that - - foreach (var direction in RandomDirections()) + while (OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0) { - // Can't spill < 1 reagent so stop overflowing - if ((ReagentUnit.Epsilon * neighborPuddles.Count) == overflowAmount) + foreach (var next in nextPuddles.ToArray()) { - break; - } + nextPuddles.Remove(next); - // If we found an existing puddle on that tile then we don't need to spawn a new one - var noSpawn = false; + next._overflown = true; + overflownPuddles.Add(next); - foreach (var entity in _snapGrid.GetInDir(direction)) - { - // Don't overflow to walls - if (entity.TryGetComponent(out CollidableComponent collidableComponent) && - collidableComponent.CollisionLayer == (int) CollisionGroup.Impassable) + var adjacentPuddles = next.GetAllAdjacentOverflow().ToArray(); + if (OverflowLeft <= ReagentUnit.Epsilon * adjacentPuddles.Length) { - noSpawn = true; break; } - if (!entity.TryGetComponent(out PuddleComponent puddleComponent)) + if (adjacentPuddles.Length == 0) { continue; } - // If we've overflowed recently don't include it - noSpawn = true; - // TODO: PauseManager - if ((DateTime.Now - puddleComponent._lastOverflow).TotalSeconds < 1) + var numberOfAdjacent = ReagentUnit.New(adjacentPuddles.Length); + var overflowSplit = OverflowLeft / numberOfAdjacent; + foreach (var adjacent in adjacentPuddles) { - break; + var adjacentPuddle = adjacent(); + var quantity = ReagentUnit.Min(overflowSplit, adjacentPuddle.OverflowVolume); + var spillAmount = _contents.SplitSolution(quantity); + + adjacentPuddle.TryAddSolution(spillAmount, false, false, false); + nextPuddles.Add(adjacentPuddle); } - - neighborPuddles.Add(entity); - break; } - - if (noSpawn) - { - continue; - } - - var grid = _snapGrid.DirectionToGrid(direction); - // We'll just add the co-ordinates as we need to figure out how many puddles we need to spawn first - var entityManager = IoCManager.Resolve(); - neighborPuddles.Add(entityManager.SpawnEntity(Owner.Prototype.ID, grid)); } - if (neighborPuddles.Count == 0) + foreach (var puddle in overflownPuddles) { - return; + puddle._overflown = false; } - - var spillAmount = overflowAmount / ReagentUnit.New(neighborPuddles.Count); - - SpillToNeighbours(neighborPuddles, spillAmount); } // TODO: Move the below to SnapGrid? @@ -319,14 +306,69 @@ namespace Content.Server.GameObjects.Components.Fluids } } - private void SpillToNeighbours(IEnumerable neighbors, ReagentUnit spillAmount) + /// + /// Tries to get an adjacent coordinate to overflow to, unless it is blocked by a wall on the + /// same tile or the tile is empty + /// + /// The direction to get the puddle from, respective to this one + /// The puddle that was found or is to be created, or null if there + /// is a wall in the way + /// true if a puddle was found or created, false otherwise + private bool TryGetAdjacentOverflow(Direction direction, out Func puddle) { - foreach (var neighborPuddle in neighbors) + puddle = default; + + var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); + + // If space return early, let that spill go out into the void + var tileRef = mapGrid.GetTileRef(Owner.Transform.GridPosition.Offset(direction.ToVec())); + if (tileRef.Tile.IsEmpty) { - var solution = _contents.SplitSolution(spillAmount); + return false; + } - neighborPuddle.GetComponent().TryAddSolution(solution, false, false); + foreach (var entity in _snapGrid.GetInDir(direction)) + { + if (entity.TryGetComponent(out CollidableComponent collidable) && + (collidable.CollisionLayer & (int) CollisionGroup.Impassable) != 0) + { + puddle = default; + return false; + } + if (entity.TryGetComponent(out PuddleComponent existingPuddle)) + { + if (existingPuddle._overflown) + { + return false; + } + + puddle = () => existingPuddle; + } + } + + if (puddle == default) + { + var grid = _snapGrid.DirectionToGrid(direction); + var entityManager = IoCManager.Resolve(); + puddle = () => entityManager.SpawnEntity(Owner.Prototype.ID, grid).GetComponent(); + } + + return true; + } + + /// + /// Finds or creates adjacent puddles in random directions from this one + /// + /// Enumerable of the puddles found or to be created + private IEnumerable> GetAllAdjacentOverflow() + { + foreach (var direction in RandomDirections()) + { + if (TryGetAdjacentOverflow(direction, out var puddle)) + { + yield return puddle; + } } } } diff --git a/Content.Shared/Chemistry/ReagentUnit.cs b/Content.Shared/Chemistry/ReagentUnit.cs index ae71925bbb..42ed972187 100644 --- a/Content.Shared/Chemistry/ReagentUnit.cs +++ b/Content.Shared/Chemistry/ReagentUnit.cs @@ -175,6 +175,21 @@ namespace Content.Shared.Chemistry return a < b ? a : b; } + public static ReagentUnit Max(ReagentUnit a, ReagentUnit b) + { + return a > b ? a : b; + } + + public static ReagentUnit Clamp(ReagentUnit reagent, ReagentUnit min, ReagentUnit max) + { + if (min > max) + { + throw new ArgumentException($"{nameof(min)} {min} cannot be larger than {nameof(max)} {max}"); + } + + return reagent < min ? min : reagent > max ? max : reagent; + } + public override bool Equals(object obj) { return obj is ReagentUnit unit && diff --git a/Resources/Prototypes/Entities/Items/Consumables/drinks.yml b/Resources/Prototypes/Entities/Items/Consumables/drinks.yml index b9cc3f8703..ea781a7c0d 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/drinks.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/drinks.yml @@ -15,6 +15,7 @@ state: icon - type: Icon state: icon + - type: CanSpill - type: entity parent: DrinkBase diff --git a/Resources/Prototypes/Entities/Items/Consumables/drinks_bottles.yml b/Resources/Prototypes/Entities/Items/Consumables/drinks_bottles.yml index a21df8bba8..d6c8f8fd3c 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/drinks_bottles.yml @@ -12,6 +12,7 @@ state: icon - type: Icon state: icon + - type: CanSpill - type: entity parent: DrinkBottleBaseFull diff --git a/Resources/Prototypes/Entities/Items/Consumables/drinks_cans.yml b/Resources/Prototypes/Entities/Items/Consumables/drinks_cans.yml index 69012c5941..22ed64edb5 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/drinks_cans.yml @@ -17,6 +17,7 @@ state: icon - type: Icon state: icon + - type: CanSpill - type: entity parent: DrinkCanBaseFull diff --git a/Resources/Prototypes/Entities/Items/Consumables/drinks_cups.yml b/Resources/Prototypes/Entities/Items/Consumables/drinks_cups.yml index 731f82cc4a..8de5c5613d 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/drinks_cups.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/drinks_cups.yml @@ -15,6 +15,7 @@ state: icon - type: Icon state: icon + - type: CanSpill - type: entity parent: DrinkBaseCup diff --git a/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml b/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml index 7ab52bc806..aef848cc43 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/kitchen_reagent_containers.yml @@ -19,3 +19,4 @@ - type: Icon sprite: Objects/Food/flour.rsi state: icon + - type: CanSpill diff --git a/Resources/Prototypes/Entities/Items/Consumables/trash_drinks.yml b/Resources/Prototypes/Entities/Items/Consumables/trash_drinks.yml index 6cbf5b0be0..db5caf9db1 100644 --- a/Resources/Prototypes/Entities/Items/Consumables/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Items/Consumables/trash_drinks.yml @@ -16,6 +16,7 @@ transferAmount: 5 - type: Drink isOpen: true + - type: CanSpill # Containers diff --git a/Resources/Prototypes/Entities/Items/chemistry.yml b/Resources/Prototypes/Entities/Items/chemistry.yml index 0a2c4c2ec9..d84156dc0b 100644 --- a/Resources/Prototypes/Entities/Items/chemistry.yml +++ b/Resources/Prototypes/Entities/Items/chemistry.yml @@ -17,6 +17,7 @@ caps: 27 - type: Pourable transferAmount: 5.0 + - type: CanSpill - type: entity name: large beaker @@ -37,6 +38,7 @@ caps: 27 - type: Pourable transferAmount: 5.0 + - type: CanSpill - type: entity name: dropper @@ -55,6 +57,7 @@ caps: 19 - type: Pourable transferAmount: 5.0 + - type: CanSpill - type: entity name: syringe @@ -73,3 +76,4 @@ caps: 19 - type: Injector injectOnly: false + - type: CanSpill diff --git a/Resources/Prototypes/Entities/janitor.yml b/Resources/Prototypes/Entities/janitor.yml index 311079ca23..9e2116f7a0 100644 --- a/Resources/Prototypes/Entities/janitor.yml +++ b/Resources/Prototypes/Entities/janitor.yml @@ -53,6 +53,7 @@ - type: Physics mass: 5 Anchored: false + - type: CanSpill - type: entity parent: BaseItem @@ -84,6 +85,7 @@ - type: Physics mass: 5 Anchored: false + - type: CanSpill - type: entity name: wet floor sign