2023-04-10 15:37:03 +10:00
using Content.Server.Administration.Logs ;
2023-12-29 04:47:43 -08:00
using Content.Server.Chemistry.Containers.EntitySystems ;
2023-04-10 15:37:03 +10:00
using Content.Server.DoAfter ;
2021-10-27 09:24:18 +01:00
using Content.Server.Fluids.Components ;
2023-04-10 15:37:03 +10:00
using Content.Server.Spreader ;
2023-10-19 12:34:31 -07:00
using Content.Shared.Chemistry ;
using Content.Shared.Chemistry.Components ;
2024-01-30 22:52:35 -07:00
using Content.Shared.Chemistry.Components.SolutionManager ;
2023-10-14 09:45:28 -07:00
using Content.Shared.Chemistry.EntitySystems ;
2023-10-19 12:34:31 -07:00
using Content.Shared.Chemistry.Reaction ;
2023-04-06 14:20:48 -07:00
using Content.Shared.Chemistry.Reagent ;
2023-04-10 15:37:03 +10:00
using Content.Shared.Database ;
2023-10-19 12:34:31 -07:00
using Content.Shared.Effects ;
2021-11-03 16:48:03 -07:00
using Content.Shared.FixedPoint ;
2021-10-27 09:24:18 +01:00
using Content.Shared.Fluids ;
2023-04-10 15:37:03 +10:00
using Content.Shared.Fluids.Components ;
2023-05-14 01:45:53 +10:00
using Content.Shared.Friction ;
2023-08-01 01:15:17 +02:00
using Content.Shared.IdentityManagement ;
2023-10-19 12:34:31 -07:00
using Content.Shared.Maps ;
using Content.Shared.Movement.Components ;
using Content.Shared.Movement.Systems ;
using Content.Shared.Popups ;
using Content.Shared.Slippery ;
2022-07-10 02:28:37 -07:00
using Content.Shared.StepTrigger.Components ;
using Content.Shared.StepTrigger.Systems ;
2023-12-29 04:47:43 -08:00
using Robust.Server.Audio ;
2024-01-11 13:22:56 +00:00
using Robust.Shared.Collections ;
2022-11-15 12:30:59 +01:00
using Robust.Shared.Map ;
2023-04-10 15:37:03 +10:00
using Robust.Shared.Map.Components ;
2021-10-27 09:24:18 +01:00
using Robust.Shared.Player ;
2023-01-12 16:41:40 +13:00
using Robust.Shared.Prototypes ;
2023-04-06 14:20:48 -07:00
using Robust.Shared.Random ;
2023-04-10 15:37:03 +10:00
using Robust.Shared.Timing ;
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
namespace Content.Server.Fluids.EntitySystems ;
/// <summary>
/// Handles solutions on floors. Also handles the spreader logic for where the solution overflows a specified volume.
/// </summary>
public sealed partial class PuddleSystem : SharedPuddleSystem
2021-10-27 09:24:18 +01:00
{
2024-01-11 13:22:56 +00:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2023-04-10 15:37:03 +10:00
[Dependency] private readonly IGameTiming _timing = default ! ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
[Dependency] private readonly IRobustRandom _random = default ! ;
2023-08-11 03:44:52 +10:00
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default ! ;
2023-04-10 15:37:03 +10:00
[Dependency] private readonly AudioSystem _audio = default ! ;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default ! ;
[Dependency] private readonly EntityLookupSystem _lookup = default ! ;
[Dependency] private readonly ReactiveSystem _reactive = default ! ;
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
2023-08-11 03:44:52 +10:00
[Dependency] private readonly SharedColorFlashEffectSystem _color = default ! ;
2023-04-10 15:37:03 +10:00
[Dependency] private readonly SharedPopupSystem _popups = default ! ;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default ! ;
2023-08-11 03:44:52 +10:00
[Dependency] private readonly StepTriggerSystem _stepTrigger = default ! ;
2023-06-03 22:34:43 +03:00
[Dependency] private readonly SlowContactsSystem _slowContacts = default ! ;
2023-08-11 03:44:52 +10:00
[Dependency] private readonly TileFrictionController _tile = default ! ;
2023-04-10 15:37:03 +10:00
2023-09-05 09:55:10 +12:00
[ValidatePrototypeId<ReagentPrototype>]
private const string Blood = "Blood" ;
[ValidatePrototypeId<ReagentPrototype>]
private const string Slime = "Slime" ;
[ValidatePrototypeId<ReagentPrototype>]
2023-10-31 20:41:25 +00:00
private const string CopperBlood = "CopperBlood" ;
2023-09-05 09:55:10 +12:00
2024-03-03 00:36:36 -05:00
private static string [ ] _standoutReagents = [ Blood , Slime , CopperBlood ] ;
2023-09-05 09:55:10 +12:00
2024-03-03 00:36:36 -05:00
public static readonly float PuddleVolume = 1000 ;
2023-04-10 15:37:03 +10: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.
2024-03-03 00:36:36 -05:00
private HashSet < EntityUid > _deletionQueue = [ ] ;
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
private EntityQuery < PuddleComponent > _puddleQuery ;
2023-04-10 15:37:03 +10:00
/ *
* TODO : Need some sort of way to do blood slash / vomit solution spill on its own
* This would then evaporate into the puddle tile below
* /
/// <inheritdoc/>
public override void Initialize ( )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
base . Initialize ( ) ;
2024-01-11 13:22:56 +00:00
_puddleQuery = GetEntityQuery < PuddleComponent > ( ) ;
2023-04-10 15:37:03 +10:00
// Shouldn't need re-anchoring.
SubscribeLocalEvent < PuddleComponent , AnchorStateChangedEvent > ( OnAnchorChanged ) ;
2023-12-29 04:47:43 -08:00
SubscribeLocalEvent < PuddleComponent , SolutionContainerChangedEvent > ( OnSolutionUpdate ) ;
2023-04-10 15:37:03 +10:00
SubscribeLocalEvent < PuddleComponent , ComponentInit > ( OnPuddleInit ) ;
SubscribeLocalEvent < PuddleComponent , SpreadNeighborsEvent > ( OnPuddleSpread ) ;
SubscribeLocalEvent < PuddleComponent , SlipEvent > ( OnPuddleSlip ) ;
SubscribeLocalEvent < EvaporationComponent , MapInitEvent > ( OnEvaporationMapInit ) ;
2023-01-12 16:41:40 +13:00
2023-04-10 15:37:03 +10:00
InitializeTransfers ( ) ;
}
2022-11-15 12:30:59 +01:00
2023-12-29 04:47:43 -08:00
private void OnPuddleSpread ( Entity < PuddleComponent > entity , ref SpreadNeighborsEvent args )
2023-04-10 15:37:03 +10:00
{
2024-01-11 13:22:56 +00:00
// Overflow is the source of the overflowing liquid. This contains the excess fluid above overflow limit (20u)
2023-12-29 04:47:43 -08:00
var overflow = GetOverflowSolution ( entity . Owner , entity . Comp ) ;
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
if ( overflow . Volume = = FixedPoint2 . Zero )
2021-10-27 09:24:18 +01:00
{
2023-12-29 04:47:43 -08:00
RemCompDeferred < ActiveEdgeSpreaderComponent > ( entity ) ;
2023-04-10 15:37:03 +10:00
return ;
}
2021-10-27 09:24:18 +01:00
2023-04-23 18:20:03 +10:00
// For overflows, we never go to a fully evaporative tile just to avoid continuously having to mop it.
2024-01-11 13:22:56 +00:00
// First we go to free tiles.
// Need to go even if we have a little remainder to avoid solution sploshing around internally
// for ages.
if ( args . NeighborFreeTiles . Count > 0 & & args . Updates > 0 )
{
_random . Shuffle ( args . NeighborFreeTiles ) ;
var spillAmount = overflow . Volume / args . NeighborFreeTiles . Count ;
foreach ( var neighbor in args . NeighborFreeTiles )
{
var split = overflow . SplitSolution ( spillAmount ) ;
TrySpillAt ( neighbor . Grid . GridTileToLocal ( neighbor . Tile ) , split , out _ , false ) ;
args . Updates - - ;
if ( args . Updates < = 0 )
break ;
}
RemCompDeferred < ActiveEdgeSpreaderComponent > ( entity ) ;
return ;
}
// Then we overflow to neighbors with overflow capacity
2023-04-10 15:37:03 +10:00
if ( args . Neighbors . Count > 0 )
2023-01-01 19:03:26 +13:00
{
2024-01-11 13:22:56 +00:00
var resolvedNeighbourSolutions = new ValueList < ( Solution neighborSolution , PuddleComponent puddle , EntityUid neighbor ) > ( ) ;
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
// Resolve all our neighbours first, so we can use their properties to decide who to operate on first.
2023-04-10 15:37:03 +10:00
foreach ( var neighbor in args . Neighbors )
{
2024-01-11 13:22:56 +00:00
if ( ! _puddleQuery . TryGetComponent ( neighbor , out var puddle ) | |
! _solutionContainerSystem . ResolveSolution ( neighbor , puddle . SolutionName , ref puddle . Solution ,
out var neighborSolution ) | |
2023-04-23 18:20:03 +10:00
CanFullyEvaporate ( neighborSolution ) )
2023-04-10 15:37:03 +10:00
{
continue ;
}
2024-01-11 13:22:56 +00:00
resolvedNeighbourSolutions . Add (
( neighborSolution , puddle , neighbor )
) ;
}
// We want to deal with our neighbours by lowest current volume to highest, as this allows us to fill up our low points quickly.
resolvedNeighbourSolutions . Sort (
( x , y ) = >
x . neighborSolution . Volume . CompareTo ( y . neighborSolution . Volume ) ) ;
// Overflow to neighbors with remaining space.
foreach ( var ( neighborSolution , puddle , neighbor ) in resolvedNeighbourSolutions )
{
// Water doesn't flow uphill
if ( neighborSolution . Volume > = ( overflow . Volume + puddle . OverflowVolume ) )
{
continue ;
}
// Work out how much we could send into this neighbour without overflowing it, and send up to that much
2023-04-26 21:29:31 +10:00
var remaining = puddle . OverflowVolume - neighborSolution . Volume ;
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
// If we can't send anything, then skip this neighbour
2023-04-10 15:37:03 +10:00
if ( remaining < = FixedPoint2 . Zero )
continue ;
2024-01-11 13:22:56 +00:00
// We don't want to spill over to make high points either.
if ( neighborSolution . Volume + remaining > = ( overflow . Volume + puddle . OverflowVolume ) )
{
continue ;
}
2023-04-10 15:37:03 +10:00
var split = overflow . SplitSolution ( remaining ) ;
2024-01-11 13:22:56 +00:00
if ( puddle . Solution ! = null & & ! _solutionContainerSystem . TryAddSolution ( puddle . Solution . Value , split ) )
2023-04-10 15:37:03 +10:00
continue ;
args . Updates - - ;
2023-10-02 07:56:41 +11:00
EnsureComp < ActiveEdgeSpreaderComponent > ( neighbor ) ;
2023-04-10 15:37:03 +10:00
if ( args . Updates < = 0 )
break ;
}
2024-01-11 13:22:56 +00:00
// If there is nothing left to overflow from our tile, then we'll stop this tile being a active spreader
2023-04-10 15:37:03 +10:00
if ( overflow . Volume = = FixedPoint2 . Zero )
2023-01-01 19:03:26 +13:00
{
2023-12-29 04:47:43 -08:00
RemCompDeferred < ActiveEdgeSpreaderComponent > ( entity ) ;
2023-04-10 15:37:03 +10:00
return ;
2023-01-01 19:03:26 +13:00
}
}
2024-01-11 13:22:56 +00:00
// Then we go to anything else.
if ( overflow . Volume > FixedPoint2 . Zero & & args . Neighbors . Count > 0 & & args . Updates > 0 )
2021-10-27 09:24:18 +01:00
{
2024-01-11 13:22:56 +00:00
var resolvedNeighbourSolutions =
new ValueList < ( Solution neighborSolution , PuddleComponent puddle , EntityUid neighbor ) > ( ) ;
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
// Keep track of the total volume in the area
FixedPoint2 totalVolume = 0 ;
// Resolve all our neighbours so that we can use their properties to decide who to act on first
foreach ( var neighbor in args . Neighbors )
2023-04-10 15:37:03 +10:00
{
2024-01-11 13:22:56 +00:00
if ( ! _puddleQuery . TryGetComponent ( neighbor , out var puddle ) | |
! _solutionContainerSystem . ResolveSolution ( neighbor , puddle . SolutionName , ref puddle . Solution ,
out var neighborSolution ) | |
CanFullyEvaporate ( neighborSolution ) )
{
continue ;
}
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
resolvedNeighbourSolutions . Add ( ( neighborSolution , puddle , neighbor ) ) ;
totalVolume + = neighborSolution . Volume ;
2023-04-10 15:37:03 +10:00
}
2024-01-11 13:22:56 +00:00
// We should act on neighbours by their total volume.
resolvedNeighbourSolutions . Sort (
( x , y ) = >
x . neighborSolution . Volume . CompareTo ( y . neighborSolution . Volume )
) ;
2021-10-27 09:24:18 +01:00
2024-01-11 13:22:56 +00:00
// Overflow to neighbors with remaining total allowed space (1000u) above the overflow volume (20u).
foreach ( var ( neighborSolution , puddle , neighbor ) in resolvedNeighbourSolutions )
2023-04-10 15:37:03 +10:00
{
2024-01-11 13:22:56 +00:00
// What the source tiles current volume is.
var sourceCurrentVolume = overflow . Volume + puddle . OverflowVolume ;
// Water doesn't flow uphill
if ( neighborSolution . Volume > = sourceCurrentVolume )
{
continue ;
}
// We're in the low point in this area, let the neighbour tiles have a chance to spread to us first.
var idealAverageVolume =
( totalVolume + overflow . Volume + puddle . OverflowVolume ) / ( args . Neighbors . Count + 1 ) ;
if ( idealAverageVolume > sourceCurrentVolume )
{
continue ;
}
// Work our how far off the ideal average this neighbour is.
var spillThisNeighbor = idealAverageVolume - neighborSolution . Volume ;
// Skip if we want to spill negative amounts of fluid to this neighbour
if ( spillThisNeighbor < FixedPoint2 . Zero )
2023-04-10 15:37:03 +10:00
{
continue ;
}
2023-04-06 14:20:48 -07:00
2024-01-11 13:22:56 +00:00
// Try to send them as much towards the average ideal as we can
var split = overflow . SplitSolution ( spillThisNeighbor ) ;
2023-04-10 15:37:03 +10:00
2024-01-11 13:22:56 +00:00
// If we can't do it, move on.
if ( puddle . Solution ! = null & & ! _solutionContainerSystem . TryAddSolution ( puddle . Solution . Value , split ) )
2023-04-10 15:37:03 +10:00
continue ;
2023-04-06 14:20:48 -07:00
2024-01-11 13:22:56 +00:00
// If we succeed, then ensure that this neighbour is also able to spread it's overflow onwards
2023-10-02 07:56:41 +11:00
EnsureComp < ActiveEdgeSpreaderComponent > ( neighbor ) ;
2023-04-10 15:37:03 +10:00
args . Updates - - ;
2023-04-06 14:20:48 -07:00
2023-04-10 15:37:03 +10:00
if ( args . Updates < = 0 )
break ;
}
2023-04-06 14:20:48 -07:00
}
2023-04-10 15:37:03 +10:00
// Add the remainder back
2023-12-29 04:47:43 -08:00
if ( _solutionContainerSystem . ResolveSolution ( entity . Owner , entity . Comp . SolutionName , ref entity . Comp . Solution ) )
2021-10-27 09:24:18 +01:00
{
2023-12-29 04:47:43 -08:00
_solutionContainerSystem . TryAddSolution ( entity . Comp . Solution . Value , overflow ) ;
2023-04-10 15:37:03 +10:00
}
}
2023-01-01 19:03:26 +13:00
2023-12-29 04:47:43 -08:00
private void OnPuddleSlip ( Entity < PuddleComponent > entity , ref SlipEvent args )
2023-04-10 15:37:03 +10:00
{
// Reactive entities have a chance to get a touch reaction from slipping on a puddle
// (i.e. it is implied they fell face first onto it or something)
2024-02-01 11:39:10 +01:00
if ( ! HasComp < ReactiveComponent > ( args . Slipped ) | | HasComp < SlidingComponent > ( args . Slipped ) )
2023-04-10 15:37:03 +10:00
return ;
// Eventually probably have some system of 'body coverage' to tweak the probability but for now just 0.5
// (implying that spacemen have a 50% chance to either land on their ass or their face)
if ( ! _random . Prob ( 0.5f ) )
return ;
2024-01-11 13:22:56 +00:00
if ( ! _solutionContainerSystem . ResolveSolution ( entity . Owner , entity . Comp . SolutionName , ref entity . Comp . Solution ,
out var solution ) )
2023-04-10 15:37:03 +10:00
return ;
2023-12-29 04:47:43 -08:00
_popups . PopupEntity ( Loc . GetString ( "puddle-component-slipped-touch-reaction" , ( "puddle" , entity . Owner ) ) ,
2023-04-10 15:37:03 +10:00
args . Slipped , args . Slipped , PopupType . SmallCaution ) ;
// Take 15% of the puddle solution
2023-12-29 04:47:43 -08:00
var splitSol = _solutionContainerSystem . SplitSolution ( entity . Comp . Solution . Value , solution . Volume * 0.15f ) ;
2023-04-10 15:37:03 +10:00
_reactive . DoEntityReaction ( args . Slipped , splitSol , ReactionMethod . Touch ) ;
}
/// <inheritdoc/>
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
foreach ( var ent in _deletionQueue )
{
Del ( ent ) ;
}
2024-01-11 13:22:56 +00:00
2023-04-10 15:37:03 +10:00
_deletionQueue . Clear ( ) ;
TickEvaporation ( ) ;
}
2023-01-01 19:03:26 +13:00
2023-12-29 04:47:43 -08:00
private void OnPuddleInit ( Entity < PuddleComponent > entity , ref ComponentInit args )
2023-04-10 15:37:03 +10:00
{
2024-01-11 13:22:56 +00:00
_solutionContainerSystem . EnsureSolution ( entity . Owner , entity . Comp . SolutionName , FixedPoint2 . New ( PuddleVolume ) ,
out _ ) ;
2023-04-10 15:37:03 +10:00
}
2023-12-29 04:47:43 -08:00
private void OnSolutionUpdate ( Entity < PuddleComponent > entity , ref SolutionContainerChangedEvent args )
2023-04-10 15:37:03 +10:00
{
2023-12-29 04:47:43 -08:00
if ( args . SolutionId ! = entity . Comp . SolutionName )
2023-04-10 15:37:03 +10:00
return ;
if ( args . Solution . Volume < = 0 )
{
2023-12-29 04:47:43 -08:00
_deletionQueue . Add ( entity ) ;
2023-04-10 15:37:03 +10:00
return ;
}
2023-12-29 04:47:43 -08:00
_deletionQueue . Remove ( entity ) ;
UpdateSlip ( entity , entity . Comp , args . Solution ) ;
UpdateSlow ( entity , args . Solution ) ;
UpdateEvaporation ( entity , args . Solution ) ;
UpdateAppearance ( entity , entity . Comp ) ;
2023-04-10 15:37:03 +10:00
}
2024-01-11 13:22:56 +00:00
private void UpdateAppearance ( EntityUid uid , PuddleComponent ? puddleComponent = null ,
AppearanceComponent ? appearance = null )
2023-04-10 15:37:03 +10:00
{
if ( ! Resolve ( uid , ref puddleComponent , ref appearance , false ) )
{
return ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
var volume = FixedPoint2 . Zero ;
Color color = Color . White ;
2024-01-11 13:22:56 +00:00
if ( _solutionContainerSystem . ResolveSolution ( uid , puddleComponent . SolutionName , ref puddleComponent . Solution ,
out var solution ) )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
volume = solution . Volume / puddleComponent . OverflowVolume ;
// Make blood stand out more
// Kinda EH
// Could potentially do alpha per-solution but future problem.
2023-09-05 09:55:10 +12:00
color = solution . GetColorWithout ( _prototypeManager , _standoutReagents ) ;
2023-04-10 15:37:03 +10:00
color = color . WithAlpha ( 0.7f ) ;
2023-09-05 09:55:10 +12:00
foreach ( var standout in _standoutReagents )
2021-10-27 09:24:18 +01:00
{
2023-09-05 09:55:10 +12:00
var quantity = solution . GetTotalPrototypeQuantity ( standout ) ;
if ( quantity < = FixedPoint2 . Zero )
2023-04-10 15:37:03 +10:00
continue ;
var interpolateValue = quantity . Float ( ) / solution . Volume . Float ( ) ;
2024-01-11 13:22:56 +00:00
color = Color . InterpolateBetween ( color ,
_prototypeManager . Index < ReagentPrototype > ( standout ) . SubstanceColor , interpolateValue ) ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
}
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
_appearance . SetData ( uid , PuddleVisuals . CurrentVolume , volume . Float ( ) , appearance ) ;
_appearance . SetData ( uid , PuddleVisuals . SolutionColor , color , appearance ) ;
}
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
private void UpdateSlip ( EntityUid entityUid , PuddleComponent component , Solution solution )
{
var isSlippery = false ;
// The base sprite is currently at 0.3 so we require at least 2nd tier to be slippery or else it's too hard to see.
var amountRequired = FixedPoint2 . New ( component . OverflowVolume . Float ( ) * LowThreshold ) ;
var slipperyAmount = FixedPoint2 . Zero ;
2022-02-10 15:07:21 -06:00
2023-09-05 09:55:10 +12:00
foreach ( var ( reagent , quantity ) in solution . Contents )
2023-04-10 15:37:03 +10:00
{
2023-09-05 09:55:10 +12:00
var reagentProto = _prototypeManager . Index < ReagentPrototype > ( reagent . Prototype ) ;
2023-04-10 15:37:03 +10:00
if ( reagentProto . Slippery )
2022-12-19 20:40:53 -06:00
{
2023-09-05 09:55:10 +12:00
slipperyAmount + = quantity ;
2023-04-10 15:37:03 +10:00
if ( slipperyAmount > amountRequired )
{
isSlippery = true ;
break ;
}
2022-12-19 20:40:53 -06:00
}
2023-04-10 15:37:03 +10:00
}
2022-02-04 20:26:11 -06:00
2023-04-10 15:37:03 +10:00
if ( isSlippery )
{
var comp = EnsureComp < StepTriggerComponent > ( entityUid ) ;
_stepTrigger . SetActive ( entityUid , true , comp ) ;
2023-05-14 01:45:53 +10:00
var friction = EnsureComp < TileFrictionModifierComponent > ( entityUid ) ;
_tile . SetModifier ( entityUid , TileFrictionController . DefaultFriction * 0.5f , friction ) ;
2023-04-10 15:37:03 +10:00
}
else if ( TryComp < StepTriggerComponent > ( entityUid , out var comp ) )
{
_stepTrigger . SetActive ( entityUid , false , comp ) ;
2023-05-14 01:45:53 +10:00
RemCompDeferred < TileFrictionModifierComponent > ( entityUid ) ;
2023-04-10 15:37:03 +10:00
}
}
2023-01-12 16:41:40 +13:00
2023-06-03 22:34:43 +03:00
private void UpdateSlow ( EntityUid uid , Solution solution )
{
var maxViscosity = 0f ;
2023-09-05 09:55:10 +12:00
foreach ( var ( reagent , _ ) in solution . Contents )
2023-06-03 22:34:43 +03:00
{
2023-09-05 09:55:10 +12:00
var reagentProto = _prototypeManager . Index < ReagentPrototype > ( reagent . Prototype ) ;
2023-06-03 22:34:43 +03:00
maxViscosity = Math . Max ( maxViscosity , reagentProto . Viscosity ) ;
}
2024-01-11 13:22:56 +00:00
2023-06-03 22:34:43 +03:00
if ( maxViscosity > 0 )
{
var comp = EnsureComp < SlowContactsComponent > ( uid ) ;
var speed = 1 - maxViscosity ;
_slowContacts . ChangeModifiers ( uid , speed , comp ) ;
}
else
{
RemComp < SlowContactsComponent > ( uid ) ;
}
}
2023-12-29 04:47:43 -08:00
private void OnAnchorChanged ( Entity < PuddleComponent > entity , ref AnchorStateChangedEvent args )
2023-04-10 15:37:03 +10:00
{
if ( ! args . Anchored )
2023-12-29 04:47:43 -08:00
QueueDel ( entity ) ;
2023-04-10 15:37:03 +10:00
}
/// <summary>
/// Gets the current volume of the given puddle, which may not necessarily be PuddleVolume.
/// </summary>
public FixedPoint2 CurrentVolume ( EntityUid uid , PuddleComponent ? puddleComponent = null )
{
if ( ! Resolve ( uid , ref puddleComponent ) )
return FixedPoint2 . Zero ;
2024-01-11 13:22:56 +00:00
return _solutionContainerSystem . ResolveSolution ( uid , puddleComponent . SolutionName , ref puddleComponent . Solution ,
out var solution )
2023-04-10 15:37:03 +10:00
? solution . Volume
: FixedPoint2 . Zero ;
}
/// <summary>
/// Try to add solution to <paramref name="puddleUid"/>.
/// </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 ,
bool sound = true ,
bool checkForOverflow = true ,
2024-01-30 22:52:35 -07:00
PuddleComponent ? puddleComponent = null ,
SolutionContainerManagerComponent ? sol = null )
2023-04-10 15:37:03 +10:00
{
2024-01-30 22:52:35 -07:00
if ( ! Resolve ( puddleUid , ref puddleComponent , ref sol ) )
2023-04-10 15:37:03 +10:00
return false ;
2024-01-30 22:52:35 -07:00
_solutionContainerSystem . EnsureAllSolutions ( ( puddleUid , sol ) ) ;
2023-04-10 15:37:03 +10:00
if ( addedSolution . Volume = = 0 | |
2024-01-11 13:22:56 +00:00
! _solutionContainerSystem . ResolveSolution ( puddleUid , puddleComponent . SolutionName ,
ref puddleComponent . Solution ) )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
return false ;
}
2023-12-29 04:47:43 -08:00
_solutionContainerSystem . AddSolution ( puddleComponent . Solution . Value , addedSolution ) ;
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
if ( checkForOverflow & & IsOverflowing ( puddleUid , puddleComponent ) )
{
2023-10-02 07:56:41 +11:00
EnsureComp < ActiveEdgeSpreaderComponent > ( puddleUid ) ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
if ( ! sound )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
return true ;
}
2023-11-27 22:12:34 +11:00
_audio . PlayPvs ( puddleComponent . SpillSound , puddleUid ) ;
2023-04-10 15:37:03 +10:00
return true ;
}
/// <summary>
/// Whether adding this solution to this puddle would overflow.
/// </summary>
public bool WouldOverflow ( EntityUid uid , Solution solution , PuddleComponent ? puddle = null )
{
if ( ! Resolve ( uid , ref puddle ) )
return false ;
return CurrentVolume ( uid , puddle ) + solution . Volume > puddle . OverflowVolume ;
}
/// <summary>
/// Whether adding this solution to this puddle would overflow.
/// </summary>
private bool IsOverflowing ( EntityUid uid , PuddleComponent ? puddle = null )
{
if ( ! Resolve ( uid , ref puddle ) )
return false ;
return CurrentVolume ( uid , puddle ) > puddle . OverflowVolume ;
}
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
/// <summary>
/// Gets the solution amount above the overflow threshold for the puddle.
/// </summary>
public Solution GetOverflowSolution ( EntityUid uid , PuddleComponent ? puddle = null )
{
2024-01-11 13:22:56 +00:00
if ( ! Resolve ( uid , ref puddle ) | |
! _solutionContainerSystem . ResolveSolution ( uid , puddle . SolutionName , ref puddle . Solution ) )
2023-04-10 15:37:03 +10:00
{
return new Solution ( 0 ) ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
// TODO: This is going to fail with struct solutions.
var remaining = puddle . OverflowVolume ;
2024-01-11 13:22:56 +00:00
var split = _solutionContainerSystem . SplitSolution ( puddle . Solution . Value ,
CurrentVolume ( uid , puddle ) - remaining ) ;
2023-04-10 15:37:03 +10:00
return split ;
}
#region Spill
/// <summary>
/// First splashes reagent on reactive entities near the spilling entity, then spills the rest regularly to a
/// puddle. This is intended for 'destructive' spills, like when entities are destroyed or thrown.
/// </summary>
public bool TrySplashSpillAt ( EntityUid uid ,
EntityCoordinates coordinates ,
Solution solution ,
out EntityUid puddleUid ,
bool sound = true ,
EntityUid ? user = null )
{
puddleUid = EntityUid . Invalid ;
if ( solution . Volume = = 0 )
return false ;
2023-08-05 14:44:18 +03:00
var targets = new List < EntityUid > ( ) ;
2023-10-19 12:34:31 -07:00
var reactive = new HashSet < Entity < ReactiveComponent > > ( ) ;
_lookup . GetEntitiesInRange ( coordinates , 1.0f , reactive ) ;
2023-04-10 15:37:03 +10:00
// Get reactive entities nearby--if there are some, it'll spill a bit on them instead.
2023-10-19 12:34:31 -07:00
foreach ( var ent in reactive )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
// sorry! no overload for returning uid, so .owner must be used
var owner = ent . Owner ;
// between 5 and 30%
var splitAmount = solution . Volume * _random . NextFloat ( 0.05f , 0.30f ) ;
var splitSolution = solution . SplitSolution ( splitAmount ) ;
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
if ( user ! = null )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
_adminLogger . Add ( LogType . Landed ,
$"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):entity} which splashed a solution {SolutionContainerSystem.ToPrettyString(solution):solution} onto {ToPrettyString(owner):target}" ) ;
2021-10-27 09:24:18 +01:00
}
2023-08-05 14:44:18 +03:00
targets . Add ( owner ) ;
2023-04-10 15:37:03 +10:00
_reactive . DoEntityReaction ( owner , splitSolution , ReactionMethod . Touch ) ;
2024-01-11 13:22:56 +00:00
_popups . PopupEntity (
Loc . GetString ( "spill-land-spilled-on-other" , ( "spillable" , uid ) ,
( "target" , Identity . Entity ( owner , EntityManager ) ) ) , owner , PopupType . SmallCaution ) ;
2023-04-10 15:37:03 +10:00
}
2023-01-12 16:41:40 +13:00
2024-01-11 13:22:56 +00:00
_color . RaiseEffect ( solution . GetColor ( _prototypeManager ) , targets ,
Filter . Pvs ( uid , entityManager : EntityManager ) ) ;
2023-08-05 14:44:18 +03:00
2023-04-10 15:37:03 +10:00
return TrySpillAt ( coordinates , solution , out puddleUid , sound ) ;
}
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
/// <summary>
/// Spills solution at the specified coordinates.
/// Will add to an existing puddle if present or create a new one if not.
/// </summary>
public bool TrySpillAt ( EntityCoordinates coordinates , Solution solution , out EntityUid puddleUid , bool sound = true )
{
if ( solution . Volume = = 0 )
{
puddleUid = EntityUid . Invalid ;
return false ;
}
2021-10-27 09:24:18 +01:00
2023-04-10 15:37:03 +10:00
if ( ! _mapManager . TryGetGrid ( coordinates . GetGridUid ( EntityManager ) , out var mapGrid ) )
{
puddleUid = EntityUid . Invalid ;
return false ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
return TrySpillAt ( mapGrid . GetTileRef ( coordinates ) , solution , out puddleUid , sound ) ;
}
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
/// <summary>
/// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/>
/// </summary>
2024-01-11 13:22:56 +00:00
public bool TrySpillAt ( EntityUid uid , Solution solution , out EntityUid puddleUid , bool sound = true ,
TransformComponent ? transformComponent = null )
2023-04-10 15:37:03 +10:00
{
if ( ! Resolve ( uid , ref transformComponent , false ) )
{
puddleUid = EntityUid . Invalid ;
return false ;
}
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
return TrySpillAt ( transformComponent . Coordinates , solution , out puddleUid , sound : sound ) ;
}
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
/// <summary>
/// <see cref="TrySpillAt(Robust.Shared.Map.EntityCoordinates,Content.Shared.Chemistry.Components.Solution,out Robust.Shared.GameObjects.EntityUid,bool)"/>
/// </summary>
2024-01-11 13:22:56 +00:00
public bool TrySpillAt ( TileRef tileRef , Solution solution , out EntityUid puddleUid , bool sound = true ,
bool tileReact = true )
2023-04-10 15:37:03 +10:00
{
if ( solution . Volume < = 0 )
{
puddleUid = EntityUid . Invalid ;
return false ;
}
// If space return early, let that spill go out into the void
2023-07-05 16:59:48 +03:00
if ( tileRef . Tile . IsEmpty | | tileRef . IsSpace ( _tileDefMan ) )
2023-04-10 15:37:03 +10:00
{
puddleUid = EntityUid . Invalid ;
return false ;
}
// Let's not spill to invalid grids.
var gridId = tileRef . GridUid ;
if ( ! _mapManager . TryGetGrid ( gridId , out var mapGrid ) )
{
puddleUid = EntityUid . Invalid ;
return false ;
}
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
if ( tileReact )
{
// First, do all tile reactions
2023-10-31 21:39:12 +01:00
DoTileReactions ( tileRef , solution ) ;
2022-11-15 12:30:59 +01:00
}
2023-04-10 15:37:03 +10:00
// Tile reactions used up everything.
if ( solution . Volume = = FixedPoint2 . Zero )
2021-10-27 09:24:18 +01:00
{
2023-04-10 15:37:03 +10:00
puddleUid = EntityUid . Invalid ;
return false ;
2022-11-15 12:30:59 +01:00
}
2023-04-10 15:37:03 +10:00
// Get normalized co-ordinate for spill location and spill it in the centre
// TODO: Does SnapGrid or something else already do this?
var anchored = mapGrid . GetAnchoredEntitiesEnumerator ( tileRef . GridIndices ) ;
var puddleQuery = GetEntityQuery < PuddleComponent > ( ) ;
var sparklesQuery = GetEntityQuery < EvaporationSparkleComponent > ( ) ;
while ( anchored . MoveNext ( out var ent ) )
2022-11-15 12:30:59 +01:00
{
2023-04-10 15:37:03 +10:00
// If there's existing sparkles then delete it
if ( sparklesQuery . TryGetComponent ( ent , out var sparkles ) )
{
QueueDel ( ent . Value ) ;
continue ;
}
if ( ! puddleQuery . TryGetComponent ( ent , out var puddle ) )
continue ;
if ( TryAddSolution ( ent . Value , solution , sound , puddleComponent : puddle ) )
{
2023-10-02 07:56:41 +11:00
EnsureComp < ActiveEdgeSpreaderComponent > ( ent . Value ) ;
2023-04-10 15:37:03 +10:00
}
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
puddleUid = ent . Value ;
return true ;
2022-11-15 12:30:59 +01:00
}
2023-04-10 15:37:03 +10:00
var coords = mapGrid . GridTileToLocal ( tileRef . GridIndices ) ;
puddleUid = EntityManager . SpawnEntity ( "Puddle" , coords ) ;
EnsureComp < PuddleComponent > ( puddleUid ) ;
if ( TryAddSolution ( puddleUid , solution , sound ) )
2022-11-15 12:30:59 +01:00
{
2023-10-02 07:56:41 +11:00
EnsureComp < ActiveEdgeSpreaderComponent > ( puddleUid ) ;
2023-04-10 15:37:03 +10:00
}
2024-01-11 13:22:56 +00:00
2023-04-10 15:37:03 +10:00
return true ;
}
#endregion
2023-10-31 21:39:12 +01:00
public void DoTileReactions ( TileRef tileRef , Solution solution )
{
for ( var i = solution . Contents . Count - 1 ; i > = 0 ; i - - )
{
var ( reagent , quantity ) = solution . Contents [ i ] ;
var proto = _prototypeManager . Index < ReagentPrototype > ( reagent . Prototype ) ;
var removed = proto . ReactionTile ( tileRef , quantity ) ;
if ( removed < = FixedPoint2 . Zero )
continue ;
solution . RemoveReagent ( reagent , removed ) ;
}
}
2023-04-10 15:37:03 +10:00
/// <summary>
/// Tries to get the relevant puddle entity for a tile.
/// </summary>
public bool TryGetPuddle ( TileRef tile , out EntityUid puddleUid )
{
puddleUid = EntityUid . Invalid ;
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
if ( ! TryComp < MapGridComponent > ( tile . GridUid , out var grid ) )
return false ;
2022-11-15 12:30:59 +01:00
2023-04-10 15:37:03 +10:00
var anc = grid . GetAnchoredEntitiesEnumerator ( tile . GridIndices ) ;
var puddleQuery = GetEntityQuery < PuddleComponent > ( ) ;
while ( anc . MoveNext ( out var ent ) )
{
if ( ! puddleQuery . HasComponent ( ent . Value ) )
continue ;
puddleUid = ent . Value ;
return true ;
2021-10-27 09:24:18 +01:00
}
2023-04-10 15:37:03 +10:00
return false ;
2021-10-27 09:24:18 +01:00
}
}