Fix reagent spill errors (#1180)
Co-authored-by: ComicIronic <comicironic@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
@@ -11,6 +12,7 @@ using Robust.Shared.GameObjects.Components;
|
|||||||
using Robust.Shared.GameObjects.Components.Transform;
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.Interfaces.Random;
|
using Robust.Shared.Interfaces.Random;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Maths;
|
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)
|
// 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
|
// 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";
|
public override string Name => "Puddle";
|
||||||
|
|
||||||
private CancellationTokenSource _evaporationToken;
|
private CancellationTokenSource _evaporationToken;
|
||||||
private ReagentUnit _evaporateThreshold; // How few <Solution Quantity> we can hold prior to self-destructing
|
private ReagentUnit _evaporateThreshold; // How few <Solution Quantity> we can hold prior to self-destructing
|
||||||
private float _evaporateTime;
|
private float _evaporateTime;
|
||||||
private string _spillSound;
|
private string _spillSound;
|
||||||
private DateTime _lastOverflow = DateTime.Now;
|
|
||||||
private SpriteComponent _spriteComponent;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this puddle is currently overflowing onto its neighbors
|
||||||
|
/// </summary>
|
||||||
|
private bool _overflown;
|
||||||
|
|
||||||
|
private SpriteComponent _spriteComponent;
|
||||||
private SnapGridComponent _snapGrid;
|
private SnapGridComponent _snapGrid;
|
||||||
|
|
||||||
public ReagentUnit MaxVolume
|
public ReagentUnit MaxVolume
|
||||||
@@ -65,6 +75,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
public ReagentUnit OverflowVolume => _overflowVolume;
|
public ReagentUnit OverflowVolume => _overflowVolume;
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private ReagentUnit _overflowVolume;
|
private ReagentUnit _overflowVolume;
|
||||||
|
private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume;
|
||||||
|
|
||||||
private SolutionComponent _contents;
|
private SolutionComponent _contents;
|
||||||
private int _spriteVariants;
|
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
|
// 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)
|
if (solution.TotalVolume == 0)
|
||||||
{
|
{
|
||||||
@@ -126,7 +137,12 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
|
|
||||||
|
if (checkForOverflow)
|
||||||
|
{
|
||||||
CheckOverflow();
|
CheckOverflow();
|
||||||
|
}
|
||||||
|
|
||||||
if (checkForEvaporate)
|
if (checkForEvaporate)
|
||||||
{
|
{
|
||||||
CheckEvaporate();
|
CheckEvaporate();
|
||||||
@@ -198,7 +214,6 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
_spriteComponent.Color = newColor;
|
_spriteComponent.Color = newColor;
|
||||||
|
|
||||||
_spriteComponent.Dirty();
|
_spriteComponent.Dirty();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -206,80 +221,52 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckOverflow()
|
private void CheckOverflow()
|
||||||
{
|
{
|
||||||
if (CurrentVolume <= _overflowVolume)
|
if (CurrentVolume <= _overflowVolume || _overflown)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Essentially:
|
var nextPuddles = new List<PuddleComponent>() {this};
|
||||||
// Spill at least 1 solution to each neighbor (so most of the time each puddle is getting 1 max)
|
var overflownPuddles = new List<PuddleComponent>();
|
||||||
// If there's no puddle at the neighbor then add one.
|
|
||||||
|
|
||||||
// Setup
|
while (OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0)
|
||||||
// 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<IEntity>(8);
|
|
||||||
|
|
||||||
// Will overflow to each neighbor; if it already has a puddle entity then add to that
|
|
||||||
|
|
||||||
foreach (var direction in RandomDirections())
|
|
||||||
{
|
{
|
||||||
// Can't spill < 1 reagent so stop overflowing
|
foreach (var next in nextPuddles.ToArray())
|
||||||
if ((ReagentUnit.Epsilon * neighborPuddles.Count) == overflowAmount)
|
{
|
||||||
|
nextPuddles.Remove(next);
|
||||||
|
|
||||||
|
next._overflown = true;
|
||||||
|
overflownPuddles.Add(next);
|
||||||
|
|
||||||
|
var adjacentPuddles = next.GetAllAdjacentOverflow().ToArray();
|
||||||
|
if (OverflowLeft <= ReagentUnit.Epsilon * adjacentPuddles.Length)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found an existing puddle on that tile then we don't need to spawn a new one
|
if (adjacentPuddles.Length == 0)
|
||||||
var noSpawn = false;
|
|
||||||
|
|
||||||
foreach (var entity in _snapGrid.GetInDir(direction))
|
|
||||||
{
|
|
||||||
// Don't overflow to walls
|
|
||||||
if (entity.TryGetComponent(out CollidableComponent collidableComponent) &&
|
|
||||||
collidableComponent.CollisionLayer == (int) CollisionGroup.Impassable)
|
|
||||||
{
|
|
||||||
noSpawn = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entity.TryGetComponent(out PuddleComponent puddleComponent))
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've overflowed recently don't include it
|
var numberOfAdjacent = ReagentUnit.New(adjacentPuddles.Length);
|
||||||
noSpawn = true;
|
var overflowSplit = OverflowLeft / numberOfAdjacent;
|
||||||
// TODO: PauseManager
|
foreach (var adjacent in adjacentPuddles)
|
||||||
if ((DateTime.Now - puddleComponent._lastOverflow).TotalSeconds < 1)
|
|
||||||
{
|
{
|
||||||
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);
|
foreach (var puddle in overflownPuddles)
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noSpawn)
|
|
||||||
{
|
{
|
||||||
continue;
|
puddle._overflown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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<IEntityManager>();
|
|
||||||
neighborPuddles.Add(entityManager.SpawnEntity(Owner.Prototype.ID, grid));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neighborPuddles.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spillAmount = overflowAmount / ReagentUnit.New(neighborPuddles.Count);
|
|
||||||
|
|
||||||
SpillToNeighbours(neighborPuddles, spillAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move the below to SnapGrid?
|
// TODO: Move the below to SnapGrid?
|
||||||
@@ -319,14 +306,69 @@ namespace Content.Server.GameObjects.Components.Fluids
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpillToNeighbours(IEnumerable<IEntity> neighbors, ReagentUnit spillAmount)
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="direction">The direction to get the puddle from, respective to this one</param>
|
||||||
|
/// <param name="puddle">The puddle that was found or is to be created, or null if there
|
||||||
|
/// is a wall in the way</param>
|
||||||
|
/// <returns>true if a puddle was found or created, false otherwise</returns>
|
||||||
|
private bool TryGetAdjacentOverflow(Direction direction, out Func<PuddleComponent> 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<PuddleComponent>().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<IEntityManager>();
|
||||||
|
puddle = () => entityManager.SpawnEntity(Owner.Prototype.ID, grid).GetComponent<PuddleComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds or creates adjacent puddles in random directions from this one
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Enumerable of the puddles found or to be created</returns>
|
||||||
|
private IEnumerable<Func<PuddleComponent>> GetAllAdjacentOverflow()
|
||||||
|
{
|
||||||
|
foreach (var direction in RandomDirections())
|
||||||
|
{
|
||||||
|
if (TryGetAdjacentOverflow(direction, out var puddle))
|
||||||
|
{
|
||||||
|
yield return puddle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,21 @@ namespace Content.Shared.Chemistry
|
|||||||
return a < b ? a : b;
|
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)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
return obj is ReagentUnit unit &&
|
return obj is ReagentUnit unit &&
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
state: icon
|
state: icon
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: icon
|
state: icon
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: DrinkBase
|
parent: DrinkBase
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
state: icon
|
state: icon
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: icon
|
state: icon
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: DrinkBottleBaseFull
|
parent: DrinkBottleBaseFull
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
state: icon
|
state: icon
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: icon
|
state: icon
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: DrinkCanBaseFull
|
parent: DrinkCanBaseFull
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
state: icon
|
state: icon
|
||||||
- type: Icon
|
- type: Icon
|
||||||
state: icon
|
state: icon
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: DrinkBaseCup
|
parent: DrinkBaseCup
|
||||||
|
|||||||
@@ -19,3 +19,4 @@
|
|||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Objects/Food/flour.rsi
|
sprite: Objects/Food/flour.rsi
|
||||||
state: icon
|
state: icon
|
||||||
|
- type: CanSpill
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
transferAmount: 5
|
transferAmount: 5
|
||||||
- type: Drink
|
- type: Drink
|
||||||
isOpen: true
|
isOpen: true
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
|
|
||||||
# Containers
|
# Containers
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
caps: 27
|
caps: 27
|
||||||
- type: Pourable
|
- type: Pourable
|
||||||
transferAmount: 5.0
|
transferAmount: 5.0
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: large beaker
|
name: large beaker
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
caps: 27
|
caps: 27
|
||||||
- type: Pourable
|
- type: Pourable
|
||||||
transferAmount: 5.0
|
transferAmount: 5.0
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: dropper
|
name: dropper
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
caps: 19
|
caps: 19
|
||||||
- type: Pourable
|
- type: Pourable
|
||||||
transferAmount: 5.0
|
transferAmount: 5.0
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: syringe
|
name: syringe
|
||||||
@@ -73,3 +76,4 @@
|
|||||||
caps: 19
|
caps: 19
|
||||||
- type: Injector
|
- type: Injector
|
||||||
injectOnly: false
|
injectOnly: false
|
||||||
|
- type: CanSpill
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
Anchored: false
|
Anchored: false
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
Anchored: false
|
Anchored: false
|
||||||
|
- type: CanSpill
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: wet floor sign
|
name: wet floor sign
|
||||||
|
|||||||
Reference in New Issue
Block a user