Add puddles / reagent spills (#743)
This commit is contained in:
120
Content.Server/GameObjects/Components/Fluids/BucketComponent.cs
Normal file
120
Content.Server/GameObjects/Components/Fluids/BucketComponent.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
/// <summary>
|
||||
/// Can a mop click on this entity and dump its fluids
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BucketComponent : Component, IAttackBy
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Bucket";
|
||||
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _contents.MaxVolume;
|
||||
set => _contents.MaxVolume = value;
|
||||
}
|
||||
|
||||
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
|
||||
|
||||
private SolutionComponent _contents;
|
||||
|
||||
private string _sound;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataFieldCached(ref _sound, "sound", "/Audio/effects/Fluids/watersplash.ogg");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_contents = Owner.GetComponent<SolutionComponent>();
|
||||
}
|
||||
|
||||
private bool TryGiveToMop(MopComponent mopComponent)
|
||||
{
|
||||
// Let's fill 'er up
|
||||
// If this is called the mop should be empty but just in case we'll do Max - Current
|
||||
var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume);
|
||||
var solution = _contents.SplitSolution(transferAmount);
|
||||
if (!mopComponent.Contents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_sound == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Owner.TryGetComponent(out SoundComponent soundComponent);
|
||||
soundComponent?.Play(_sound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AttackBy(AttackByEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.AttackWith.TryGetComponent(out MopComponent mopComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Give to the mop if it's empty
|
||||
if (mopComponent.CurrentVolume == 0)
|
||||
{
|
||||
if (!TryGiveToMop(mopComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Splish"));
|
||||
return true;
|
||||
}
|
||||
|
||||
var transferAmount = ReagentUnit.Min(mopComponent.CurrentVolume, MaxVolume - CurrentVolume);
|
||||
if (transferAmount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var solution = mopComponent.Contents.SplitSolution(transferAmount);
|
||||
if (!_contents.TryAddSolution(solution))
|
||||
{
|
||||
//This really shouldn't happen
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Give some visual feedback shit's happening (for anyone who can't hear sound)
|
||||
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Sploosh"));
|
||||
|
||||
if (_sound == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Owner.TryGetComponent(out SoundComponent soundComponent);
|
||||
soundComponent?.Play(_sound);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class CanSpillComponent : Component
|
||||
{
|
||||
public override string Name => "CanSpill";
|
||||
// TODO: If the Owner doesn't have a SolutionComponent straight up just have this remove itself?
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class FillTargetVerb : Verb<CanSpillComponent>
|
||||
{
|
||||
protected override string GetText(IEntity user, CanSpillComponent component)
|
||||
{
|
||||
return "Spill liquid";
|
||||
}
|
||||
|
||||
protected override VerbVisibility GetVisibility(IEntity user, CanSpillComponent component)
|
||||
{
|
||||
if (component.Owner.TryGetComponent(out SolutionComponent solutionComponent))
|
||||
{
|
||||
return solutionComponent.CurrentVolume > ReagentUnit.Zero ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||
}
|
||||
|
||||
return VerbVisibility.Invisible;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, CanSpillComponent component)
|
||||
{
|
||||
var solutionComponent = component.Owner.GetComponent<SolutionComponent>();
|
||||
// Need this as when we split the component's owner may be deleted
|
||||
var entityLocation = component.Owner.Transform.GridPosition;
|
||||
var solution = solutionComponent.SplitSolution(solutionComponent.CurrentVolume);
|
||||
SpillHelper.SpillAt(entityLocation, solution, "PuddleSmear");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Content.Server/GameObjects/Components/Fluids/MopComponent.cs
Normal file
112
Content.Server/GameObjects/Components/Fluids/MopComponent.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
/// <summary>
|
||||
/// For cleaning up puddles
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class MopComponent : Component, IAfterAttack
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Mop";
|
||||
internal SolutionComponent Contents => _contents;
|
||||
private SolutionComponent _contents;
|
||||
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _contents.MaxVolume;
|
||||
set => _contents.MaxVolume = value;
|
||||
}
|
||||
|
||||
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
|
||||
|
||||
// Currently there's a separate amount for pickup and dropoff so
|
||||
// Picking up a puddle requires multiple clicks
|
||||
// Dumping in a bucket requires 1 click
|
||||
// Long-term you'd probably use a cooldown and start the pickup once we have some form of global cooldown
|
||||
public ReagentUnit PickupAmount => _pickupAmount;
|
||||
private ReagentUnit _pickupAmount;
|
||||
|
||||
private string _pickupSound;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataFieldCached(ref _pickupSound, "pickup_sound", "/Audio/effects/Fluids/slosh.ogg");
|
||||
// The turbo mop will pickup more
|
||||
serializer.DataFieldCached(ref _pickupAmount, "pickup_amount", ReagentUnit.New(5));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_contents = Owner.GetComponent<SolutionComponent>();
|
||||
|
||||
}
|
||||
|
||||
void IAfterAttack.AfterAttack(AfterAttackEventArgs eventArgs)
|
||||
{
|
||||
Solution solution;
|
||||
if (eventArgs.Attacked == null)
|
||||
{
|
||||
if (CurrentVolume <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Drop the liquid on the mop on to the ground I guess? Potentially change by design
|
||||
// Maybe even use a toggle mode instead of "Pickup" and "dropoff"
|
||||
solution = _contents.SplitSolution(CurrentVolume);
|
||||
SpillHelper.SpillAt(eventArgs.ClickLocation, solution, "PuddleSmear");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventArgs.Attacked.TryGetComponent(out PuddleComponent puddleComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Essentially pickup either:
|
||||
// - _pickupAmount,
|
||||
// - whatever's left in the puddle, or
|
||||
// - whatever we can still hold (whichever's smallest)
|
||||
var transferAmount = ReagentUnit.Min(ReagentUnit.New(5), puddleComponent.CurrentVolume, MaxVolume - CurrentVolume);
|
||||
if (transferAmount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
solution = puddleComponent.SplitSolution(transferAmount);
|
||||
// Probably don't recolor a mop? Could work, if we layered it maybe
|
||||
if (!_contents.TryAddSolution(solution, false, true))
|
||||
{
|
||||
// I can't imagine why this would happen
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
// Give some visual feedback shit's happening (for anyone who can't hear sound)
|
||||
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Swish"));
|
||||
|
||||
if (_pickupSound == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.TryGetComponent(out SoundComponent soundComponent);
|
||||
soundComponent?.Play(_pickupSound);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
333
Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs
Normal file
333
Content.Server/GameObjects/Components/Fluids/PuddleComponent.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Timer = Robust.Shared.Timers.Timer;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
/// <summary>
|
||||
/// Puddle on a floor
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PuddleComponent : Component
|
||||
{
|
||||
// Current design: Something calls the SpillHelper.Spill, that will either
|
||||
// A) Add to an existing puddle at the location (normalised to tile-center) or
|
||||
// B) add a new one
|
||||
// From this every time a puddle is spilt on it will try and overflow to its neighbours if possible,
|
||||
// and also update its appearance based on volume level (opacity) and chemistry color
|
||||
// Small puddles will evaporate after a set delay
|
||||
|
||||
// TODO: 'leaves fluidtracks', probably in a separate component for stuff like gibb chunks?;
|
||||
// TODO: Add stuff like slipping -> probably in a separate component (for stuff like bananas) and using BumpEntMsg
|
||||
|
||||
// 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
|
||||
|
||||
public override string Name => "Puddle";
|
||||
|
||||
private CancellationTokenSource _evaporationToken;
|
||||
private ReagentUnit _evaporateThreshold; // How few <Solution Quantity> we can hold prior to self-destructing
|
||||
private float _evaporateTime;
|
||||
private string _spillSound;
|
||||
private DateTime _lastOverflow = DateTime.Now;
|
||||
private SpriteComponent _spriteComponent;
|
||||
|
||||
private SnapGridComponent _snapGrid;
|
||||
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
get => _contents.MaxVolume;
|
||||
set => _contents.MaxVolume = value;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
|
||||
|
||||
// Volume at which the fluid will try to spill to adjacent components
|
||||
// Currently a random number, potentially change
|
||||
public ReagentUnit OverflowVolume => _overflowVolume;
|
||||
[ViewVariables]
|
||||
private ReagentUnit _overflowVolume;
|
||||
|
||||
private SolutionComponent _contents;
|
||||
private int _spriteVariants;
|
||||
// Whether the underlying solution color should be used
|
||||
private bool _recolor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataFieldCached(ref _spillSound, "spill_sound", "/Audio/effects/Fluids/splat.ogg");
|
||||
serializer.DataField(ref _overflowVolume, "overflow_volume", ReagentUnit.New(20));
|
||||
serializer.DataField(ref _evaporateTime, "evaporate_time", 600.0f);
|
||||
// Long-term probably have this based on the underlying reagents
|
||||
serializer.DataField(ref _evaporateThreshold, "evaporate_threshold", ReagentUnit.New(2));
|
||||
serializer.DataField(ref _spriteVariants, "variants", 1);
|
||||
serializer.DataField(ref _recolor, "recolor", false);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
|
||||
{
|
||||
_contents = solutionComponent;
|
||||
}
|
||||
else
|
||||
{
|
||||
_contents = Owner.AddComponent<SolutionComponent>();
|
||||
_contents.Initialize();
|
||||
}
|
||||
|
||||
_snapGrid = Owner.GetComponent<SnapGridComponent>();
|
||||
|
||||
// Smaller than 1m^3 for now but realistically this shouldn't be hit
|
||||
MaxVolume = ReagentUnit.New(1000);
|
||||
|
||||
// Random sprite state set server-side so it's consistent across all clients
|
||||
_spriteComponent = Owner.GetComponent<SpriteComponent>();
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
var randomVariant = robustRandom.Next(0, _spriteVariants - 1);
|
||||
var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension;
|
||||
|
||||
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
|
||||
_spriteComponent.Rotation = Angle.FromDegrees(robustRandom.Next(0, 359));
|
||||
// UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here
|
||||
}
|
||||
|
||||
// Flow rate should probably be controlled globally so this is it for now
|
||||
internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true)
|
||||
{
|
||||
if (solution.TotalVolume == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = _contents.TryAddSolution(solution);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateStatus();
|
||||
CheckOverflow();
|
||||
if (checkForEvaporate)
|
||||
{
|
||||
CheckEvaporate();
|
||||
}
|
||||
|
||||
UpdateAppearance();
|
||||
if (!sound)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
|
||||
entitySystemManager.GetEntitySystem<AudioSystem>().Play(_spillSound);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal Solution SplitSolution(ReagentUnit quantity)
|
||||
{
|
||||
var split = _contents.SplitSolution(quantity);
|
||||
CheckEvaporate();
|
||||
UpdateAppearance();
|
||||
return split;
|
||||
}
|
||||
|
||||
public void CheckEvaporate()
|
||||
{
|
||||
if (CurrentVolume == 0)
|
||||
{
|
||||
Owner.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
// If UpdateStatus is getting called again it means more fluid has been updated so let's just wait
|
||||
_evaporationToken?.Cancel();
|
||||
|
||||
if (CurrentVolume > _evaporateThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_evaporationToken = new CancellationTokenSource();
|
||||
|
||||
// KYS to evaporate
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_evaporateTime), CheckEvaporate, _evaporationToken.Token);
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (Owner.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Opacity based on level of fullness to overflow
|
||||
// Hard-cap lower bound for visibility reasons
|
||||
var volumeScale = (CurrentVolume.Float() / OverflowVolume.Float()) * 0.75f + 0.25f;
|
||||
var cappedScale = Math.Min(1.0f, volumeScale);
|
||||
// Color based on the underlying solutioncomponent
|
||||
Color newColor;
|
||||
if (_recolor)
|
||||
{
|
||||
newColor = _contents.SubstanceColor.WithAlpha(cappedScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
newColor = _spriteComponent.Color.WithAlpha(cappedScale);
|
||||
}
|
||||
|
||||
_spriteComponent.Color = newColor;
|
||||
|
||||
_spriteComponent.Dirty();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will overflow this entity to neighboring entities if required
|
||||
/// </summary>
|
||||
private void CheckOverflow()
|
||||
{
|
||||
if (CurrentVolume <= _overflowVolume)
|
||||
{
|
||||
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.
|
||||
|
||||
// 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<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
|
||||
if ((ReagentUnit.Epsilon * neighborPuddles.Count) == overflowAmount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If we found an existing puddle on that tile then we don't need to spawn a new one
|
||||
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;
|
||||
}
|
||||
|
||||
// If we've overflowed recently don't include it
|
||||
noSpawn = true;
|
||||
// TODO: PauseManager
|
||||
if ((DateTime.Now - puddleComponent._lastOverflow).TotalSeconds < 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
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<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?
|
||||
/// <summary>
|
||||
/// Will yield a random direction until none are left
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<Direction> RandomDirections()
|
||||
{
|
||||
var directions = new[]
|
||||
{
|
||||
Direction.East,
|
||||
Direction.SouthEast,
|
||||
Direction.South,
|
||||
Direction.SouthWest,
|
||||
Direction.West,
|
||||
Direction.NorthWest,
|
||||
Direction.North,
|
||||
Direction.NorthEast,
|
||||
};
|
||||
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
var n = directions.Length;
|
||||
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
var k = robustRandom.Next(n + 1);
|
||||
var value = directions[k];
|
||||
directions[k] = directions[n];
|
||||
directions[n] = value;
|
||||
}
|
||||
|
||||
foreach (var direction in directions)
|
||||
{
|
||||
yield return direction;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpillToNeighbours(IEnumerable<IEntity> neighbors, ReagentUnit spillAmount)
|
||||
{
|
||||
foreach (var neighborPuddle in neighbors)
|
||||
{
|
||||
var solution = _contents.SplitSolution(spillAmount);
|
||||
|
||||
neighborPuddle.GetComponent<PuddleComponent>().TryAddSolution(solution, false, false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
Content.Server/GameObjects/Components/Fluids/SpillHelper.cs
Normal file
90
Content.Server/GameObjects/Components/Fluids/SpillHelper.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Fluids
|
||||
{
|
||||
public static class SpillHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Spills the specified solution at the entity's location if possible.
|
||||
/// </summary>
|
||||
/// <param name="entity">Entity location to spill at</param>
|
||||
/// <param name="solution">Initial solution for the prototype</param>
|
||||
/// <param name="prototype">Prototype to use</param>
|
||||
internal static void SpillAt(IEntity entity, Solution solution, string prototype)
|
||||
{
|
||||
var entityLocation = entity.Transform.GridPosition;
|
||||
SpillAt(entityLocation, solution, prototype);
|
||||
}
|
||||
|
||||
// Other functions will be calling this one
|
||||
|
||||
/// <summary>
|
||||
/// Spills solution at the specified grid co-ordinates
|
||||
/// </summary>
|
||||
/// <param name="gridCoordinates"></param>
|
||||
/// <param name="solution">Initial solution for the prototype</param>
|
||||
/// <param name="prototype">Prototype to use</param>
|
||||
internal static void SpillAt(GridCoordinates gridCoordinates, Solution solution, string prototype)
|
||||
{
|
||||
if (solution.TotalVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var serverEntityManager = IoCManager.Resolve<IServerEntityManager>();
|
||||
|
||||
var mapGrid = mapManager.GetGrid(gridCoordinates.GridID);
|
||||
|
||||
// If space return early, let that spill go out into the void
|
||||
var tileRef = mapGrid.GetTileRef(gridCoordinates);
|
||||
if (tileRef.Tile.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get normalized co-ordinate for spill location and spill it in the centre
|
||||
// TODO: Does SnapGrid or something else already do this?
|
||||
var spillTileMapGrid = mapManager.GetGrid(gridCoordinates.GridID);
|
||||
var spillTileRef = spillTileMapGrid.GetTileRef(gridCoordinates).GridIndices;
|
||||
var spillGridCoords = spillTileMapGrid.GridTileToLocal(spillTileRef);
|
||||
|
||||
var spilt = false;
|
||||
|
||||
foreach (var spillEntity in entityManager.GetEntitiesAt(spillTileMapGrid.ParentMapId, spillGridCoords.Position))
|
||||
{
|
||||
if (!spillEntity.TryGetComponent(out PuddleComponent puddleComponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!puddleComponent.TryAddSolution(solution))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
spilt = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Did we add to an existing puddle
|
||||
if (spilt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var puddle = serverEntityManager.SpawnEntity(prototype, spillGridCoords);
|
||||
puddle.GetComponent<PuddleComponent>().TryAddSolution(solution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Sound;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
@@ -7,6 +8,7 @@ using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Maths;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -119,9 +121,12 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
var split = _contents.SplitSolution(transferAmount);
|
||||
if (stomachComponent.TryTransferSolution(split))
|
||||
{
|
||||
// When we split Finish gets called which may delete the can so need to use the entity system for sound
|
||||
if (_useSound != null)
|
||||
{
|
||||
Owner.GetComponent<SoundComponent>()?.Play(_useSound);
|
||||
var entitySystemManager = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var audioSystem = entitySystemManager.GetEntitySystem<AudioSystem>();
|
||||
audioSystem.Play(_useSound);
|
||||
user.PopupMessage(user, _localizationManager.GetString("Slurp"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user