2021-10-29 13:40:15 +01:00
using Content.Server.Chemistry.EntitySystems ;
2021-10-27 09:24:18 +01:00
using Content.Server.Fluids.Components ;
using Content.Shared.Examine ;
2021-11-03 16:48:03 -07:00
using Content.Shared.FixedPoint ;
2021-10-27 09:24:18 +01:00
using Content.Shared.Fluids ;
2022-07-10 02:28:37 -07:00
using Content.Shared.StepTrigger.Components ;
using Content.Shared.StepTrigger.Systems ;
2021-10-27 09:24:18 +01:00
using JetBrains.Annotations ;
using Robust.Shared.Audio ;
2022-11-15 12:30:59 +01:00
using Robust.Shared.Map ;
2021-10-27 09:24:18 +01:00
using Robust.Shared.Player ;
2022-11-15 12:30:59 +01:00
using Solution = Content . Shared . Chemistry . Components . Solution ;
2023-01-12 16:41:40 +13:00
using Robust.Shared.Prototypes ;
2021-10-27 09:24:18 +01:00
namespace Content.Server.Fluids.EntitySystems
{
[UsedImplicitly]
public sealed class PuddleSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default ! ;
2022-02-15 03:22:26 +01:00
[Dependency] private readonly FluidSpreaderSystem _fluidSpreaderSystem = default ! ;
2022-05-18 06:07:35 +02:00
[Dependency] private readonly StepTriggerSystem _stepTrigger = default ! ;
2023-01-12 16:41:40 +13:00
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
[Dependency] private readonly IPrototypeManager _protoMan = default ! ;
public static float PuddleVolume = 1000 ;
2022-11-15 12:30:59 +01:00
2023-01-01 19:03:26 +13:00
// Using local deletion queue instead of the standard queue so that we can easily "undelete" if a puddle
// loses & then gains reagents in a single tick.
private HashSet < EntityUid > _deletionQueue = new ( ) ;
2021-10-27 09:24:18 +01:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2022-04-24 13:54:25 +10:00
// Shouldn't need re-anchoring.
2022-01-13 06:49:28 +13:00
SubscribeLocalEvent < PuddleComponent , AnchorStateChangedEvent > ( OnAnchorChanged ) ;
2021-10-27 09:24:18 +01:00
SubscribeLocalEvent < PuddleComponent , ExaminedEvent > ( HandlePuddleExamined ) ;
2022-12-19 20:40:53 -06:00
SubscribeLocalEvent < PuddleComponent , SolutionChangedEvent > ( OnSolutionUpdate ) ;
SubscribeLocalEvent < PuddleComponent , ComponentInit > ( OnPuddleInit ) ;
2021-10-27 09:24:18 +01:00
}
2023-01-01 19:03:26 +13:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
foreach ( var ent in _deletionQueue )
{
Del ( ent ) ;
}
_deletionQueue . Clear ( ) ;
}
2022-12-19 20:40:53 -06:00
private void OnPuddleInit ( EntityUid uid , PuddleComponent component , ComponentInit args )
2021-10-27 09:24:18 +01:00
{
2023-01-12 16:41:40 +13:00
_solutionContainerSystem . EnsureSolution ( uid , component . SolutionName , FixedPoint2 . New ( PuddleVolume ) , out _ ) ;
2021-10-27 09:24:18 +01:00
}
2022-12-19 20:40:53 -06:00
private void OnSolutionUpdate ( EntityUid uid , PuddleComponent component , SolutionChangedEvent args )
2021-10-27 09:24:18 +01:00
{
2023-01-01 19:03:26 +13:00
if ( args . Solution . Name ! = component . SolutionName )
return ;
2023-01-12 16:41:40 +13:00
if ( args . Solution . Volume < = 0 )
2023-01-01 19:03:26 +13:00
{
_deletionQueue . Add ( uid ) ;
return ;
}
_deletionQueue . Remove ( uid ) ;
2021-10-27 09:24:18 +01:00
UpdateSlip ( uid , component ) ;
2022-12-19 20:40:53 -06:00
UpdateAppearance ( uid , component ) ;
2021-10-27 09:24:18 +01:00
}
2022-12-19 20:40:53 -06:00
private void UpdateAppearance ( EntityUid uid , PuddleComponent ? puddleComponent = null , AppearanceComponent ? appearance = null )
2021-10-27 09:24:18 +01:00
{
2022-12-19 20:40:53 -06:00
if ( ! Resolve ( uid , ref puddleComponent , ref appearance , false )
| | EmptyHolder ( uid , puddleComponent ) )
2021-10-27 09:24:18 +01:00
{
return ;
}
// Opacity based on level of fullness to overflow
// Hard-cap lower bound for visibility reasons
2023-01-12 16:41:40 +13:00
var puddleSolution = _solutionContainerSystem . EnsureSolution ( uid , puddleComponent . SolutionName ) ;
var volumeScale = puddleSolution . Volume . Float ( ) /
2022-11-15 12:30:59 +01:00
puddleComponent . OverflowVolume . Float ( ) *
puddleComponent . OpacityModifier ;
2021-10-27 09:24:18 +01:00
2022-12-19 20:40:53 -06:00
bool isEvaporating ;
2022-02-10 15:07:21 -06:00
2022-12-19 20:40:53 -06:00
if ( TryComp ( uid , out EvaporationComponent ? evaporation )
& & evaporation . EvaporationToggle ) // if puddle is evaporating.
{
isEvaporating = true ;
}
else isEvaporating = false ;
2022-02-04 20:26:11 -06:00
2023-01-12 16:41:40 +13:00
var color = puddleSolution . GetColor ( _protoMan ) ;
_appearance . SetData ( uid , PuddleVisuals . VolumeScale , volumeScale , appearance ) ;
_appearance . SetData ( uid , PuddleVisuals . CurrentVolume , puddleSolution . Volume , appearance ) ;
_appearance . SetData ( uid , PuddleVisuals . SolutionColor , color , appearance ) ;
_appearance . SetData ( uid , PuddleVisuals . IsEvaporatingVisual , isEvaporating , appearance ) ;
2021-10-27 09:24:18 +01:00
}
private void UpdateSlip ( EntityUid entityUid , PuddleComponent puddleComponent )
{
2023-01-12 16:41:40 +13:00
var vol = CurrentVolume ( puddleComponent . Owner , puddleComponent ) ;
2021-11-03 16:48:03 -07:00
if ( ( puddleComponent . SlipThreshold = = FixedPoint2 . New ( - 1 ) | |
2023-01-12 16:41:40 +13:00
vol < puddleComponent . SlipThreshold ) & &
2022-05-18 06:07:35 +02:00
TryComp ( entityUid , out StepTriggerComponent ? stepTrigger ) )
2021-10-27 09:24:18 +01:00
{
2022-05-18 06:07:35 +02:00
_stepTrigger . SetActive ( entityUid , false , stepTrigger ) ;
2021-10-27 09:24:18 +01:00
}
2023-01-12 16:41:40 +13:00
else if ( vol > = puddleComponent . SlipThreshold )
2021-10-27 09:24:18 +01:00
{
2022-05-18 06:07:35 +02:00
var comp = EnsureComp < StepTriggerComponent > ( entityUid ) ;
_stepTrigger . SetActive ( entityUid , true , comp ) ;
2021-10-27 09:24:18 +01:00
}
}
private void HandlePuddleExamined ( EntityUid uid , PuddleComponent component , ExaminedEvent args )
{
2022-05-18 06:07:35 +02:00
if ( TryComp < StepTriggerComponent > ( uid , out var slippery ) & & slippery . Active )
2021-10-27 09:24:18 +01:00
{
args . PushText ( Loc . GetString ( "puddle-component-examine-is-slipper-text" ) ) ;
}
}
2022-01-13 06:49:28 +13:00
private void OnAnchorChanged ( EntityUid uid , PuddleComponent puddle , ref AnchorStateChangedEvent args )
2021-10-27 09:24:18 +01:00
{
2022-01-13 06:49:28 +13:00
if ( ! args . Anchored )
QueueDel ( uid ) ;
2021-10-27 09:24:18 +01:00
}
public bool EmptyHolder ( EntityUid uid , PuddleComponent ? puddleComponent = null )
{
if ( ! Resolve ( uid , ref puddleComponent ) )
return true ;
2021-12-03 15:53:09 +01:00
return ! _solutionContainerSystem . TryGetSolution ( puddleComponent . Owner , puddleComponent . SolutionName ,
2021-10-27 09:24:18 +01:00
out var solution )
| | solution . Contents . Count = = 0 ;
}
2021-11-03 16:48:03 -07:00
public FixedPoint2 CurrentVolume ( EntityUid uid , PuddleComponent ? puddleComponent = null )
2021-10-27 09:24:18 +01:00
{
if ( ! Resolve ( uid , ref puddleComponent ) )
2021-11-03 16:48:03 -07:00
return FixedPoint2 . Zero ;
2021-10-27 09:24:18 +01:00
2021-12-03 15:53:09 +01:00
return _solutionContainerSystem . TryGetSolution ( puddleComponent . Owner , puddleComponent . SolutionName ,
2021-10-27 09:24:18 +01:00
out var solution )
2023-01-12 16:41:40 +13:00
? solution . Volume
2021-11-03 16:48:03 -07:00
: FixedPoint2 . Zero ;
2021-10-27 09:24:18 +01:00
}
2022-02-15 03:22:26 +01:00
/// <summary>
2022-11-15 12:30:59 +01:00
/// Try to add solution to <paramref name="puddleUid"/>.
2022-02-15 03:22:26 +01:00
/// </summary>
/// <param name="puddleUid">Puddle to which we add</param>
/// <param name="addedSolution">Solution that is added to puddleComponent</param>
/// <param name="sound">Play sound on overflow</param>
/// <param name="checkForOverflow">Overflow on encountered values</param>
/// <param name="puddleComponent">Optional resolved PuddleComponent</param>
/// <returns></returns>
public bool TryAddSolution ( EntityUid puddleUid ,
Solution addedSolution ,
2021-10-27 09:24:18 +01:00
bool sound = true ,
bool checkForOverflow = true ,
PuddleComponent ? puddleComponent = null )
{
2022-02-15 03:22:26 +01:00
if ( ! Resolve ( puddleUid , ref puddleComponent ) )
2021-10-27 09:24:18 +01:00
return false ;
2023-01-12 16:41:40 +13:00
if ( addedSolution . Volume = = 0 | |
2021-12-03 15:53:09 +01:00
! _solutionContainerSystem . TryGetSolution ( puddleComponent . Owner , puddleComponent . SolutionName ,
2022-11-15 12:30:59 +01:00
out var solution ) )
2021-10-27 09:24:18 +01:00
{
return false ;
}
2023-01-12 16:41:40 +13:00
solution . AddSolution ( addedSolution , _protoMan ) ;
2023-01-01 19:03:26 +13:00
_solutionContainerSystem . UpdateChemicals ( puddleUid , solution , true ) ;
2023-01-12 16:41:40 +13:00
2022-11-15 12:30:59 +01:00
if ( checkForOverflow & & IsOverflowing ( puddleUid , puddleComponent ) )
2021-10-27 09:24:18 +01:00
{
2022-11-15 12:30:59 +01:00
_fluidSpreaderSystem . AddOverflowingPuddle ( puddleComponent . Owner , puddleComponent ) ;
2021-10-27 09:24:18 +01:00
}
if ( ! sound )
{
return true ;
}
2022-06-12 19:45:47 -04:00
SoundSystem . Play ( puddleComponent . SpillSound . GetSound ( ) ,
Filter . Pvs ( puddleComponent . Owner ) , puddleComponent . Owner ) ;
2021-10-27 09:24:18 +01:00
return true ;
}
2022-11-15 12:30:59 +01:00
/// <summary>
/// Given a large srcPuddle and smaller destination puddles, this method will equalize their <see cref="Solution.CurrentVolume"/>
/// </summary>
/// <param name="srcPuddle">puddle that donates liquids to other puddles</param>
/// <param name="destinationPuddles">List of puddles that we want to equalize, their puddle <see cref="Solution.CurrentVolume"/> should be less than sourcePuddleComponent</param>
/// <param name="totalVolume">Total volume of src and destination puddle</param>
/// <param name="stillOverflowing">optional parameter, that after equalization adds all still overflowing puddles.</param>
/// <param name="sourcePuddleComponent">puddleComponent for <paramref name="srcPuddle"/></param>
public void EqualizePuddles ( EntityUid srcPuddle , List < PuddleComponent > destinationPuddles ,
FixedPoint2 totalVolume ,
HashSet < EntityUid > ? stillOverflowing = null ,
PuddleComponent ? sourcePuddleComponent = null )
{
if ( ! Resolve ( srcPuddle , ref sourcePuddleComponent )
| | ! _solutionContainerSystem . TryGetSolution ( srcPuddle , sourcePuddleComponent . SolutionName ,
out var srcSolution ) )
return ;
var dividedVolume = totalVolume / ( destinationPuddles . Count + 1 ) ;
foreach ( var destPuddle in destinationPuddles )
{
if ( ! _solutionContainerSystem . TryGetSolution ( destPuddle . Owner , destPuddle . SolutionName ,
out var destSolution ) )
continue ;
2023-01-12 16:41:40 +13:00
var takeAmount = FixedPoint2 . Max ( 0 , dividedVolume - destSolution . Volume ) ;
2022-11-15 12:30:59 +01:00
TryAddSolution ( destPuddle . Owner , srcSolution . SplitSolution ( takeAmount ) , false , false , destPuddle ) ;
if ( stillOverflowing ! = null & & IsOverflowing ( destPuddle . Owner , destPuddle ) )
{
stillOverflowing . Add ( destPuddle . Owner ) ;
}
}
2023-01-12 16:41:40 +13:00
if ( stillOverflowing ! = null & & srcSolution . Volume > sourcePuddleComponent . OverflowVolume )
2022-11-15 12:30:59 +01:00
{
stillOverflowing . Add ( srcPuddle ) ;
}
}
2021-10-27 09:24:18 +01:00
/// <summary>
2022-02-15 03:22:26 +01:00
/// Whether adding this solution to this puddle would overflow.
2021-10-27 09:24:18 +01:00
/// </summary>
2022-02-15 03:22:26 +01:00
/// <param name="uid">Uid of owning entity</param>
/// <param name="puddle">Puddle to which we are adding solution</param>
/// <param name="solution">Solution we intend to add</param>
/// <returns></returns>
public bool WouldOverflow ( EntityUid uid , Solution solution , PuddleComponent ? puddle = null )
2021-10-27 09:24:18 +01:00
{
2022-02-15 03:22:26 +01:00
if ( ! Resolve ( uid , ref puddle ) )
2021-10-27 09:24:18 +01:00
return false ;
2023-01-12 16:41:40 +13:00
return CurrentVolume ( uid , puddle ) + solution . Volume > puddle . OverflowVolume ;
2022-11-15 12:30:59 +01:00
}
/// <summary>
/// Whether adding this solution to this puddle would overflow.
/// </summary>
/// <param name="uid">Uid of owning entity</param>
/// <param name="puddle">Puddle ref param</param>
/// <returns></returns>
private bool IsOverflowing ( EntityUid uid , PuddleComponent ? puddle = null )
{
if ( ! Resolve ( uid , ref puddle ) )
return false ;
return CurrentVolume ( uid , puddle ) > puddle . OverflowVolume ;
}
public PuddleComponent SpawnPuddle ( EntityUid srcUid , EntityCoordinates pos , PuddleComponent ? srcPuddleComponent = null )
{
MetaDataComponent ? metadata = null ;
Resolve ( srcUid , ref srcPuddleComponent , ref metadata ) ;
var prototype = metadata ? . EntityPrototype ? . ID ? ? "PuddleSmear" ; // TODO Spawn a entity based on another entity
var destUid = EntityManager . SpawnEntity ( prototype , pos ) ;
var destPuddle = EntityManager . EnsureComponent < PuddleComponent > ( destUid ) ;
return destPuddle ;
2021-10-27 09:24:18 +01:00
}
}
}