2021-09-06 15:49:44 +02:00
using System.Diagnostics.CodeAnalysis ;
2022-12-20 04:05:02 +00:00
using System.Linq ;
2021-10-29 13:40:15 +01:00
using Content.Server.Chemistry.Components.SolutionManager ;
using Content.Shared.Chemistry ;
2021-09-06 15:49:44 +02:00
using Content.Shared.Chemistry.Components ;
using Content.Shared.Chemistry.Reaction ;
using Content.Shared.Chemistry.Reagent ;
using Content.Shared.Examine ;
2021-11-03 16:48:03 -07:00
using Content.Shared.FixedPoint ;
2021-09-06 15:49:44 +02:00
using JetBrains.Annotations ;
using Robust.Shared.Prototypes ;
2022-12-20 04:05:02 +00:00
using Robust.Shared.Utility ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
namespace Content.Server.Chemistry.EntitySystems ;
/// <summary>
/// This event alerts system that the solution was changed
/// </summary>
public sealed class SolutionChangedEvent : EntityEventArgs
2021-09-06 15:49:44 +02:00
{
2023-01-01 19:03:26 +13:00
public readonly Solution Solution ;
public SolutionChangedEvent ( Solution solution )
{
Solution = solution ;
}
2022-02-18 03:42:39 +01:00
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Part of Chemistry system deal with SolutionContainers
/// </summary>
[UsedImplicitly]
public sealed partial class SolutionContainerSystem : EntitySystem
{
[Dependency]
private readonly SharedChemicalReactionSystem _chemistrySystem = default ! ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
[Dependency]
private readonly IPrototypeManager _prototypeManager = default ! ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
SubscribeLocalEvent < SolutionContainerManagerComponent , ComponentInit > ( InitSolution ) ;
SubscribeLocalEvent < ExaminableSolutionComponent , ExaminedEvent > ( OnExamineSolution ) ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
private void InitSolution ( EntityUid uid , SolutionContainerManagerComponent component , ComponentInit args )
{
2022-04-05 04:02:33 +12:00
foreach ( var ( name , solutionHolder ) in component . Solutions )
2021-09-06 15:49:44 +02:00
{
2022-04-05 04:02:33 +12:00
solutionHolder . Name = name ;
2022-02-18 03:42:39 +01:00
if ( solutionHolder . MaxVolume = = FixedPoint2 . Zero )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
solutionHolder . MaxVolume = solutionHolder . TotalVolume > solutionHolder . InitialMaxVolume
? solutionHolder . TotalVolume
: solutionHolder . InitialMaxVolume ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
UpdateAppearance ( uid , solutionHolder ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
}
private void OnExamineSolution ( EntityUid uid , ExaminableSolutionComponent examinableComponent ,
ExaminedEvent args )
{
SolutionContainerManagerComponent ? solutionsManager = null ;
if ( ! Resolve ( args . Examined , ref solutionsManager )
| | ! solutionsManager . Solutions . TryGetValue ( examinableComponent . Solution , out var solutionHolder ) )
return ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
if ( solutionHolder . Contents . Count = = 0 )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
args . PushText ( Loc . GetString ( "shared-solution-container-component-on-examine-empty-container" ) ) ;
return ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
var primaryReagent = solutionHolder . GetPrimaryReagentId ( ) ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
if ( ! _prototypeManager . TryIndex ( primaryReagent , out ReagentPrototype ? proto ) )
{
Logger . Error (
$"{nameof(Solution)} could not find the prototype associated with {primaryReagent}." ) ;
return ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
var colorHex = solutionHolder . Color
. ToHexNoAlpha ( ) ; //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
var messageString = "shared-solution-container-component-on-examine-main-text" ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
args . PushMarkup ( Loc . GetString ( messageString ,
( "color" , colorHex ) ,
( "wordedAmount" , Loc . GetString ( solutionHolder . Contents . Count = = 1
? "shared-solution-container-component-on-examine-worded-amount-one-reagent"
: "shared-solution-container-component-on-examine-worded-amount-multiple-reagents" ) ) ,
2022-05-12 14:06:01 +03:00
( "desc" , proto . LocalizedPhysicalDescription ) ) ) ;
2022-02-18 03:42:39 +01:00
}
2021-09-06 15:49:44 +02:00
2022-03-25 17:17:29 +13:00
public void UpdateAppearance ( EntityUid uid , Solution solution ,
2022-02-18 03:42:39 +01:00
AppearanceComponent ? appearanceComponent = null )
{
if ( ! EntityManager . EntityExists ( uid )
| | ! Resolve ( uid , ref appearanceComponent , false ) )
return ;
2021-09-06 15:49:44 +02:00
2022-06-23 19:26:54 -07:00
var filledVolumePercent = Math . Min ( 1.0f , solution . CurrentVolume . Float ( ) / solution . MaxVolume . Float ( ) ) ;
2022-02-18 03:42:39 +01:00
appearanceComponent . SetData ( SolutionContainerVisuals . VisualState ,
new SolutionContainerVisualState ( solution . Color , filledVolumePercent ) ) ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Removes part of the solution in the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="solutionHolder"></param>
/// <param name="quantity">the volume of solution to remove.</param>
/// <returns>The solution that was removed.</returns>
public Solution SplitSolution ( EntityUid targetUid , Solution solutionHolder , FixedPoint2 quantity )
{
var splitSol = solutionHolder . SplitSolution ( quantity ) ;
UpdateChemicals ( targetUid , solutionHolder ) ;
return splitSol ;
}
2021-09-06 15:49:44 +02:00
2022-12-20 04:05:02 +00:00
public void UpdateChemicals ( EntityUid uid , Solution solutionHolder , bool needsReactionsProcessing = false , ReactionMixerComponent ? mixerComponent = null )
2022-02-18 03:42:39 +01:00
{
2023-01-01 19:03:26 +13:00
DebugTools . Assert ( solutionHolder . Name ! = null & & TryGetSolution ( uid , solutionHolder . Name , out var tmp ) & & tmp = = solutionHolder ) ;
2022-02-18 03:42:39 +01:00
// Process reactions
if ( needsReactionsProcessing & & solutionHolder . CanReact )
2021-09-06 15:49:44 +02:00
{
2022-12-20 04:05:02 +00:00
_chemistrySystem . FullyReactSolution ( solutionHolder , uid , solutionHolder . MaxVolume , mixerComponent ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
UpdateAppearance ( uid , solutionHolder ) ;
2023-01-01 19:03:26 +13:00
RaiseLocalEvent ( uid , new SolutionChangedEvent ( solutionHolder ) ) ;
2022-02-18 03:42:39 +01:00
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
public void RemoveAllSolution ( EntityUid uid , Solution solutionHolder )
{
if ( solutionHolder . CurrentVolume = = 0 )
return ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
solutionHolder . RemoveAllSolution ( ) ;
UpdateChemicals ( uid , solutionHolder ) ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
public void RemoveAllSolution ( EntityUid uid , SolutionContainerManagerComponent ? solutionContainerManager = null )
{
if ( ! Resolve ( uid , ref solutionContainerManager ) )
return ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
foreach ( var solution in solutionContainerManager . Solutions . Values )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
RemoveAllSolution ( uid , solution ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Sets the capacity (maximum volume) of a solution to a new value.
/// </summary>
/// <param name="targetUid">The entity containing the solution.</param>
/// <param name="targetSolution">The solution to set the capacity of.</param>
/// <param name="capacity">The value to set the capacity of the solution to.</param>
public void SetCapacity ( EntityUid targetUid , Solution targetSolution , FixedPoint2 capacity )
{
if ( targetSolution . MaxVolume = = capacity )
return ;
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
targetSolution . MaxVolume = capacity ;
if ( capacity < targetSolution . CurrentVolume )
targetSolution . RemoveSolution ( targetSolution . CurrentVolume - capacity ) ;
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
UpdateChemicals ( targetUid , targetSolution ) ;
}
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Adds reagent of an Id to the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="targetSolution">Container to which we are adding reagent</param>
/// <param name="reagentId">The Id of the reagent to add.</param>
/// <param name="quantity">The amount of reagent to add.</param>
/// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
/// <returns>If all the reagent could be added.</returns>
public bool TryAddReagent ( EntityUid targetUid , Solution targetSolution , string reagentId , FixedPoint2 quantity ,
out FixedPoint2 acceptedQuantity , float? temperature = null )
{
acceptedQuantity = targetSolution . AvailableVolume > quantity ? quantity : targetSolution . AvailableVolume ;
targetSolution . AddReagent ( reagentId , acceptedQuantity , temperature ) ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
if ( acceptedQuantity > 0 )
UpdateChemicals ( targetUid , targetSolution , true ) ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
return acceptedQuantity = = quantity ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Removes reagent of an Id to the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="container">Solution container from which we are removing reagent</param>
/// <param name="reagentId">The Id of the reagent to remove.</param>
/// <param name="quantity">The amount of reagent to remove.</param>
/// <returns>If the reagent to remove was found in the container.</returns>
public bool TryRemoveReagent ( EntityUid targetUid , Solution ? container , string reagentId , FixedPoint2 quantity )
{
if ( container = = null | | ! container . ContainsReagent ( reagentId ) )
return false ;
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
container . RemoveReagent ( reagentId , quantity ) ;
UpdateChemicals ( targetUid , container ) ;
return true ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Adds a solution to the container, if it can fully fit.
/// </summary>
/// <param name="targetUid">entity holding targetSolution</param>
/// <param name="targetSolution">entity holding targetSolution</param>
/// <param name="addedSolution">solution being added</param>
/// <returns>If the solution could be added.</returns>
public bool TryAddSolution ( EntityUid targetUid , Solution ? targetSolution , Solution addedSolution )
{
if ( targetSolution = = null
| | ! targetSolution . CanAddSolution ( addedSolution ) | | addedSolution . TotalVolume = = 0 )
return false ;
2022-02-15 03:22:26 +01:00
2022-02-18 03:42:39 +01:00
targetSolution . AddSolution ( addedSolution ) ;
UpdateChemicals ( targetUid , targetSolution , true ) ;
return true ;
}
2022-02-15 03:22:26 +01:00
2023-01-01 19:03:26 +13:00
/// <summary>
/// Moves some quantity of a solution from one solution to another.
/// </summary>
/// <param name="sourceUid">entity holding the source solution</param>
/// <param name="targetUid">entity holding the target solution</param>
/// <param name="source">source solution</param>
/// <param name="target">target solution</param>
/// <param name="quantity">quantity of solution to move from source to target. If this is a negative number, the source & target roles are reversed.</param>
public bool TryTransferSolution ( EntityUid sourceUid , EntityUid targetUid , Solution source , Solution target , FixedPoint2 quantity )
{
if ( quantity < 0 )
return TryTransferSolution ( targetUid , sourceUid , target , source , - quantity ) ;
quantity = FixedPoint2 . Min ( quantity , target . AvailableVolume , source . CurrentVolume ) ;
if ( quantity = = 0 )
return false ;
// TODO after #12428 is merged, this should be made into a function that directly transfers reagents.
// currently this is quite inefficient.
target . AddSolution ( source . SplitSolution ( quantity ) ) ;
UpdateChemicals ( sourceUid , source , false ) ;
UpdateChemicals ( targetUid , target , true ) ;
return true ;
}
/// <summary>
/// Moves some quantity of a solution from one solution to another.
/// </summary>
/// <param name="sourceUid">entity holding the source solution</param>
/// <param name="targetUid">entity holding the target solution</param>
/// <param name="source">source solution</param>
/// <param name="target">target solution</param>
/// <param name="quantity">quantity of solution to move from source to target. If this is a negative number, the source & target roles are reversed.</param>
public bool TryTransferSolution ( EntityUid sourceUid , EntityUid targetUid , string source , string target , FixedPoint2 quantity )
{
if ( ! TryGetSolution ( sourceUid , source , out var sourceSoln ) )
return false ;
if ( ! TryGetSolution ( targetUid , target , out var targetSoln ) )
return false ;
return TryTransferSolution ( sourceUid , targetUid , sourceSoln , targetSoln , quantity ) ;
}
2022-02-18 03:42:39 +01:00
/// <summary>
/// Adds a solution to the container, overflowing the rest.
/// It will
/// Unlike <see cref="TryAddSolution"/> it will ignore size limits.
/// </summary>
/// <param name="targetUid">entity holding targetSolution</param>
/// <param name="targetSolution">The container to which we try to add.</param>
/// <param name="addedSolution">solution being added</param>
/// <param name="overflowThreshold">After addition this much will be left in targetSolution. Should be less
/// than targetSolution.TotalVolume</param>
/// <param name="overflowingSolution">Solution that exceeded overflowThreshold</param>
/// <returns></returns>
public bool TryMixAndOverflow ( EntityUid targetUid , Solution targetSolution ,
Solution addedSolution ,
FixedPoint2 overflowThreshold ,
[NotNullWhen(true)] out Solution ? overflowingSolution )
{
if ( addedSolution . TotalVolume = = 0 | | overflowThreshold > targetSolution . MaxVolume )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
overflowingSolution = null ;
return false ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
targetSolution . AddSolution ( addedSolution ) ;
UpdateChemicals ( targetUid , targetSolution , true ) ;
overflowingSolution = targetSolution . SplitSolution ( FixedPoint2 . Max ( FixedPoint2 . Zero ,
targetSolution . CurrentVolume - overflowThreshold ) ) ;
return true ;
}
public bool TryGetSolution ( EntityUid uid , string name ,
[NotNullWhen(true)] out Solution ? solution ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
solution = null ;
return false ;
}
2021-09-06 15:49:44 +02:00
2022-02-18 03:42:39 +01:00
return solutionsMgr . Solutions . TryGetValue ( name , out solution ) ;
}
/// <summary>
/// Will ensure a solution is added to given entity even if it's missing solutionContainerManager
/// </summary>
/// <param name="uid">EntityUid to which to add solution</param>
/// <param name="name">name for the solution</param>
/// <param name="solutionsMgr">solution components used in resolves</param>
/// <returns>solution</returns>
public Solution EnsureSolution ( EntityUid uid , string name ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
{
solutionsMgr = EntityManager . EnsureComponent < SolutionContainerManagerComponent > ( uid ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
if ( ! solutionsMgr . Solutions . ContainsKey ( name ) )
2021-10-27 09:24:18 +01:00
{
2022-04-05 04:02:33 +12:00
var newSolution = new Solution ( ) { Name = name } ;
2022-02-18 03:42:39 +01:00
solutionsMgr . Solutions . Add ( name , newSolution ) ;
}
2021-10-27 09:24:18 +01:00
2022-02-18 03:42:39 +01:00
return solutionsMgr . Solutions [ name ] ;
}
2021-09-06 15:49:44 +02:00
2022-03-23 14:05:10 +01:00
/// <summary>
/// Removes an amount from all reagents in a solution, adding it to a new solution.
/// </summary>
/// <param name="uid">The entity containing the solution.</param>
/// <param name="solution">The solution to remove reagents from.</param>
/// <param name="quantity">The amount to remove from every reagent in the solution.</param>
/// <returns>A new solution containing every removed reagent from the original solution.</returns>
public Solution RemoveEachReagent ( EntityUid uid , Solution solution , FixedPoint2 quantity )
2022-02-18 03:42:39 +01:00
{
if ( quantity < = 0 )
2022-03-23 14:05:10 +01:00
return new Solution ( ) ;
2021-09-06 15:49:44 +02:00
2022-03-23 14:05:10 +01:00
var removedSolution = new Solution ( ) ;
// RemoveReagent does a RemoveSwap, meaning we don't have to copy the list if we iterate it backwards.
for ( var i = solution . Contents . Count - 1 ; i > = 0 ; i - - )
2021-09-06 15:49:44 +02:00
{
2022-03-23 14:05:10 +01:00
var ( reagentId , _ ) = solution . Contents [ i ] ;
2021-09-06 15:49:44 +02:00
2022-03-23 14:05:10 +01:00
var removedQuantity = solution . RemoveReagent ( reagentId , quantity ) ;
if ( removedQuantity > 0 )
removedSolution . AddReagent ( reagentId , removedQuantity ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
UpdateChemicals ( uid , solution ) ;
2022-03-23 14:05:10 +01:00
return removedSolution ;
2022-02-18 03:42:39 +01:00
}
public FixedPoint2 GetReagentQuantity ( EntityUid owner , string reagentId )
{
var reagentQuantity = FixedPoint2 . New ( 0 ) ;
if ( EntityManager . EntityExists ( owner )
& & EntityManager . TryGetComponent ( owner , out SolutionContainerManagerComponent ? managerComponent ) )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
foreach ( var solution in managerComponent . Solutions . Values )
2021-09-06 15:49:44 +02:00
{
2022-02-18 03:42:39 +01:00
reagentQuantity + = solution . GetReagentQuantity ( reagentId ) ;
2021-09-06 15:49:44 +02:00
}
}
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
return reagentQuantity ;
}
2021-12-24 01:22:34 -08:00
2022-12-20 04:05:02 +00:00
public bool TryGetMixableSolution ( EntityUid uid ,
[NotNullWhen(true)] out Solution ? solution ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
{
solution = null ;
return false ;
}
var getMixableSolutionAttempt = new GetMixableSolutionAttemptEvent ( uid ) ;
RaiseLocalEvent ( uid , ref getMixableSolutionAttempt ) ;
if ( getMixableSolutionAttempt . MixedSolution ! = null )
{
solution = getMixableSolutionAttempt . MixedSolution ;
return true ;
}
var tryGetSolution = solutionsMgr . Solutions . FirstOrNull ( x = > x . Value . CanMix ) ;
if ( tryGetSolution . HasValue )
{
solution = tryGetSolution . Value . Value ;
return true ;
}
solution = null ;
return false ;
}
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
// Thermal energy and temperature management.
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
#region Thermal Energy and Temperature
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Sets the temperature of a solution to a new value and then checks for reaction processing.
/// </summary>
/// <param name="owner">The entity in which the solution is located.</param>
/// <param name="solution">The solution to set the temperature of.</param>
/// <param name="temperature">The new value to set the temperature to.</param>
public void SetTemperature ( EntityUid owner , Solution solution , float temperature )
{
if ( temperature = = solution . Temperature )
return ;
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
solution . Temperature = temperature ;
UpdateChemicals ( owner , solution , true ) ;
}
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
/// <summary>
/// Sets the thermal energy of a solution to a new value and then checks for reaction processing.
/// </summary>
/// <param name="owner">The entity in which the solution is located.</param>
/// <param name="solution">The solution to set the thermal energy of.</param>
/// <param name="thermalEnergy">The new value to set the thermal energy to.</param>
public void SetThermalEnergy ( EntityUid owner , Solution solution , float thermalEnergy )
{
if ( thermalEnergy = = solution . ThermalEnergy )
return ;
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
solution . ThermalEnergy = thermalEnergy ;
UpdateChemicals ( owner , solution , true ) ;
}
/// <summary>
/// Adds some thermal energy to a solution and then checks for reaction processing.
/// </summary>
/// <param name="owner">The entity in which the solution is located.</param>
/// <param name="solution">The solution to set the thermal energy of.</param>
/// <param name="thermalEnergy">The new value to set the thermal energy to.</param>
public void AddThermalEnergy ( EntityUid owner , Solution solution , float thermalEnergy )
{
if ( thermalEnergy = = 0.0f )
return ;
2021-12-24 01:22:34 -08:00
2022-02-18 03:42:39 +01:00
solution . ThermalEnergy + = thermalEnergy ;
UpdateChemicals ( owner , solution , true ) ;
2021-09-06 15:49:44 +02:00
}
2022-02-18 03:42:39 +01:00
#endregion Thermal Energy and Temperature
2021-09-06 15:49:44 +02:00
}