Fix reagent spill errors (#1180)

Co-authored-by: ComicIronic <comicironic@gmail.com>
This commit is contained in:
DrSmugleaf
2020-07-02 20:31:40 +02:00
committed by GitHub
parent ebebb3603a
commit dbd9ee1671
10 changed files with 128 additions and 59 deletions

View File

@@ -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;
}
} }
} }
} }

View File

@@ -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 &&

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -19,3 +19,4 @@
- type: Icon - type: Icon
sprite: Objects/Food/flour.rsi sprite: Objects/Food/flour.rsi
state: icon state: icon
- type: CanSpill

View File

@@ -16,6 +16,7 @@
transferAmount: 5 transferAmount: 5
- type: Drink - type: Drink
isOpen: true isOpen: true
- type: CanSpill
# Containers # Containers

View File

@@ -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

View File

@@ -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