2021-09-06 15:49:44 +02:00
using System.Diagnostics.CodeAnalysis ;
2022-12-20 04:05:02 +00:00
using System.Linq ;
2023-05-24 16:08:05 -07:00
using System.Text ;
2021-10-29 13:40:15 +01:00
using Content.Server.Chemistry.Components.SolutionManager ;
2023-08-02 03:57:22 +08:00
using Content.Server.Examine ;
2021-10-29 13:40:15 +01:00
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 ;
2023-08-02 03:57:22 +08:00
using Content.Shared.Verbs ;
2021-09-06 15:49:44 +02:00
using JetBrains.Annotations ;
2023-04-10 15:37:03 +10:00
using Robust.Shared.Audio ;
2021-09-06 15:49:44 +02:00
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]
2023-05-13 15:10:32 +12:00
private readonly ChemicalReactionSystem _chemistrySystem = default ! ;
2021-09-06 15:49:44 +02:00
2023-01-14 13:21:15 +13:00
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
2022-02-18 03:42:39 +01:00
[Dependency]
private readonly IPrototypeManager _prototypeManager = default ! ;
2023-08-02 03:57:22 +08:00
[Dependency] private readonly ExamineSystem _examine = 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 ) ;
2023-08-02 03:57:22 +08:00
SubscribeLocalEvent < ExaminableSolutionComponent , GetVerbsEvent < ExamineVerb > > ( OnSolutionExaminableVerb ) ;
2022-02-18 03:42:39 +01:00
}
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 ;
2023-01-12 16:41:40 +13:00
solutionHolder . ValidateSolution ( ) ;
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
}
2023-08-02 03:57:22 +08:00
private void OnSolutionExaminableVerb ( EntityUid uid , ExaminableSolutionComponent component , GetVerbsEvent < ExamineVerb > args )
{
if ( ! args . CanInteract | | ! args . CanAccess )
return ;
var scanEvent = new SolutionScanEvent ( ) ;
RaiseLocalEvent ( args . User , scanEvent ) ;
if ( ! scanEvent . CanScan )
{
return ;
}
SolutionContainerManagerComponent ? solutionsManager = null ;
if ( ! Resolve ( args . Target , ref solutionsManager )
| | ! solutionsManager . Solutions . TryGetValue ( component . Solution , out var solutionHolder ) )
{
return ;
}
var verb = new ExamineVerb ( )
{
Act = ( ) = >
{
var markup = GetSolutionExamine ( solutionHolder ) ;
_examine . SendExamineTooltip ( args . User , uid , markup , false , false ) ;
} ,
Text = Loc . GetString ( "scannable-solution-verb-text" ) ,
Message = Loc . GetString ( "scannable-solution-verb-message" ) ,
Category = VerbCategory . Examine ,
Icon = new SpriteSpecifier . Texture ( new ( "/Textures/Interface/VerbIcons/drink.svg.192dpi.png" ) ) ,
} ;
args . Verbs . Add ( verb ) ;
}
private FormattedMessage GetSolutionExamine ( Solution solution )
{
var msg = new FormattedMessage ( ) ;
if ( solution . Contents . Count = = 0 ) //TODO: better way to see if empty?
{
msg . AddMarkup ( Loc . GetString ( "scannable-solution-empty-container" ) ) ;
return msg ;
}
msg . AddMarkup ( Loc . GetString ( "scannable-solution-main-text" ) ) ;
foreach ( var reagent in solution )
{
if ( ! _prototypeManager . TryIndex < ReagentPrototype > ( reagent . ReagentId , out var proto ) )
{
continue ;
}
msg . PushNewline ( ) ;
msg . AddMarkup ( Loc . GetString ( "scannable-solution-chemical"
, ( "type" , proto . LocalizedName )
, ( "color" , proto . SubstanceColor . ToHexNoAlpha ( ) )
, ( "amount" , reagent . Quantity ) ) ) ;
}
return msg ;
}
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 ) )
2023-05-24 16:08:05 -07:00
{
2022-02-18 03:42:39 +01:00
return ;
2023-05-24 16:08:05 -07:00
}
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13:00
var primaryReagent = solutionHolder . GetPrimaryReagentId ( ) ;
if ( string . IsNullOrEmpty ( primaryReagent ) )
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
2023-05-24 16:08:05 -07:00
if ( ! _prototypeManager . TryIndex ( primaryReagent , out ReagentPrototype ? primary ) )
2022-02-18 03:42:39 +01:00
{
Logger . Error (
$"{nameof(Solution)} could not find the prototype associated with {primaryReagent}." ) ;
return ;
}
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13:00
var colorHex = solutionHolder . GetColor ( _prototypeManager )
2022-02-18 03:42:39 +01:00
. 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" ) ) ,
2023-05-24 16:08:05 -07:00
( "desc" , primary . LocalizedPhysicalDescription ) ) ) ;
// Add descriptions of immediately recognizable reagents, like water or beer
var recognized = new List < ReagentPrototype > ( ) ;
foreach ( var ( id , _ ) in solutionHolder )
{
if ( ! _prototypeManager . TryIndex < ReagentPrototype > ( id , out var proto ) )
{
continue ;
}
if ( ! proto . Recognizable )
{
continue ;
}
recognized . Add ( proto ) ;
}
// Skip if there's nothing recognizable
if ( recognized . Count = = 0 )
return ;
var msg = new StringBuilder ( ) ;
foreach ( var reagent in recognized )
{
string part ;
if ( reagent = = recognized [ 0 ] )
{
part = "examinable-solution-recognized-first" ;
}
else if ( reagent = = recognized [ ^ 1 ] )
{
// this loc specifically requires space to be appended, fluent doesnt support whitespace
msg . Append ( ' ' ) ;
part = "examinable-solution-recognized-last" ;
}
else
{
part = "examinable-solution-recognized-next" ;
}
msg . Append ( Loc . GetString ( part , ( "color" , reagent . SubstanceColor . ToHexNoAlpha ( ) ) ,
( "chemical" , reagent . LocalizedName ) ) ) ;
}
args . PushMarkup ( Loc . GetString ( "examinable-solution-has-recognizable-chemicals" , ( "recognizedString" , msg . ToString ( ) ) ) ) ;
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
2023-01-14 13:21:15 +13:00
_appearance . SetData ( uid , SolutionContainerVisuals . FillFraction , solution . FillFraction , appearanceComponent ) ;
_appearance . SetData ( uid , SolutionContainerVisuals . Color , solution . GetColor ( _prototypeManager ) , appearanceComponent ) ;
2023-07-01 00:10:47 +02:00
if ( solution . Name ! = null )
{
_appearance . SetData ( uid , SolutionContainerVisuals . SolutionName , solution . Name , appearanceComponent ) ;
}
2023-02-18 19:00:31 -06:00
if ( solution . GetPrimaryReagentId ( ) is { } reagent )
{
_appearance . SetData ( uid , SolutionContainerVisuals . BaseOverride , reagent , appearanceComponent ) ;
}
else
{
_appearance . SetData ( uid , SolutionContainerVisuals . BaseOverride , string . Empty , appearanceComponent ) ;
}
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>
/// 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
2023-05-16 13:26:12 +10:00
public Solution SplitStackSolution ( EntityUid targetUid , Solution solutionHolder , FixedPoint2 quantity , int stackCount )
2023-05-06 10:23:05 +03:00
{
var splitSol = solutionHolder . SplitSolution ( quantity / stackCount ) ;
2023-05-16 13:26:12 +10:00
solutionHolder . SplitSolution ( quantity - splitSol . Volume ) ;
2023-05-06 10:23:05 +03:00
UpdateChemicals ( targetUid , solutionHolder ) ;
return splitSol ;
}
2023-04-10 15:37:03 +10:00
/// <summary>
2023-04-23 06:17:21 -04:00
/// Splits a solution without the specified reagent(s).
2023-04-10 15:37:03 +10:00
/// </summary>
public Solution SplitSolutionWithout ( EntityUid targetUid , Solution solutionHolder , FixedPoint2 quantity ,
2023-04-23 06:17:21 -04:00
params string [ ] reagents )
2023-04-10 15:37:03 +10:00
{
2023-04-23 06:17:21 -04:00
var splitSol = solutionHolder . SplitSolutionWithout ( quantity , reagents ) ;
2023-04-10 15:37:03 +10:00
UpdateChemicals ( targetUid , solutionHolder ) ;
return splitSol ;
}
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 )
{
2023-01-12 16:41:40 +13:00
if ( solutionHolder . Volume = = 0 )
2022-02-18 03:42:39 +01:00
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 ;
2023-01-12 16:41:40 +13:00
if ( capacity < targetSolution . Volume )
targetSolution . RemoveSolution ( targetSolution . Volume - 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 ;
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13:00
if ( acceptedQuantity < = 0 )
return quantity = = 0 ;
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13:00
if ( temperature = = null )
targetSolution . AddReagent ( reagentId , acceptedQuantity ) ;
else
targetSolution . AddReagent ( _prototypeManager . Index < ReagentPrototype > ( reagentId ) , acceptedQuantity , temperature . Value , _prototypeManager ) ;
UpdateChemicals ( targetUid , targetSolution , true ) ;
2022-02-18 03:42:39 +01:00
return acceptedQuantity = = quantity ;
}
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13: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
2023-01-12 16:41:40 +13:00
| | ! targetSolution . CanAddSolution ( addedSolution ) | | addedSolution . Volume = = 0 )
2022-02-18 03:42:39 +01:00
return false ;
2022-02-15 03:22:26 +01:00
2023-01-12 16:41:40 +13:00
targetSolution . AddSolution ( addedSolution , _prototypeManager ) ;
2022-02-18 03:42:39 +01:00
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 ) ;
2023-01-12 16:41:40 +13:00
quantity = FixedPoint2 . Min ( quantity , target . AvailableVolume , source . Volume ) ;
2023-01-01 19:03:26 +13:00
if ( quantity = = 0 )
return false ;
2023-01-12 16:41:40 +13:00
// TODO This should be made into a function that directly transfers reagents. currently this is quite
// inefficient.
target . AddSolution ( source . SplitSolution ( quantity ) , _prototypeManager ) ;
2023-01-01 19:03:26 +13:00
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 )
{
2023-01-12 16:41:40 +13:00
if ( addedSolution . Volume = = 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
}
2023-01-12 16:41:40 +13:00
targetSolution . AddSolution ( addedSolution , _prototypeManager ) ;
2022-02-18 03:42:39 +01:00
UpdateChemicals ( targetUid , targetSolution , true ) ;
overflowingSolution = targetSolution . SplitSolution ( FixedPoint2 . Max ( FixedPoint2 . Zero ,
2023-01-12 16:41:40 +13:00
targetSolution . Volume - overflowThreshold ) ) ;
2022-02-18 03:42:39 +01:00
return true ;
}
2023-04-03 13:13:48 +12:00
public bool TryGetSolution ( [ NotNullWhen ( true ) ] EntityUid ? uid , string name ,
2022-02-18 03:42:39 +01:00
[NotNullWhen(true)] out Solution ? solution ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
2023-04-03 13:13:48 +12:00
if ( uid = = null | | ! Resolve ( uid . Value , 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 ) ;
}
2023-01-12 16:41:40 +13:00
2022-02-18 03:42:39 +01:00
/// <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>
2023-01-12 16:41:40 +13:00
/// <param name="existed">true if the solution already existed</param>
2022-02-18 03:42:39 +01:00
/// <returns>solution</returns>
2023-01-12 16:41:40 +13:00
public Solution EnsureSolution ( EntityUid uid , string name , out bool existed ,
2022-02-18 03:42:39 +01:00
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
{
solutionsMgr = EntityManager . EnsureComponent < SolutionContainerManagerComponent > ( uid ) ;
2021-09-06 15:49:44 +02:00
}
2023-01-12 16:41:40 +13:00
if ( ! solutionsMgr . Solutions . TryGetValue ( name , out var existing ) )
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 ) ;
2023-01-12 16:41:40 +13:00
existed = false ;
return newSolution ;
2022-02-18 03:42:39 +01:00
}
2021-10-27 09:24:18 +01:00
2023-01-12 16:41:40 +13:00
existed = true ;
return existing ;
2022-02-18 03:42:39 +01:00
}
2021-09-06 15:49:44 +02:00
2023-01-12 16:41:40 +13:00
/// <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 )
= > EnsureSolution ( uid , name , out _ , solutionsMgr ) ;
/// <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>
2023-01-19 03:56:45 +01:00
/// <param name="minVol">Ensures that the solution's maximum volume is larger than this value.</param>
2023-01-12 16:41:40 +13:00
/// <param name="solutionsMgr">solution components used in resolves</param>
/// <returns>solution</returns>
public Solution EnsureSolution ( EntityUid uid , string name , FixedPoint2 minVol , out bool existed ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
{
solutionsMgr = EntityManager . EnsureComponent < SolutionContainerManagerComponent > ( uid ) ;
}
if ( ! solutionsMgr . Solutions . TryGetValue ( name , out var existing ) )
{
var newSolution = new Solution ( ) { Name = name } ;
solutionsMgr . Solutions . Add ( name , newSolution ) ;
existed = false ;
newSolution . MaxVolume = minVol ;
return newSolution ;
}
existed = true ;
existing . MaxVolume = FixedPoint2 . Max ( existing . MaxVolume , minVol ) ;
return existing ;
}
public Solution EnsureSolution ( EntityUid uid , string name ,
IEnumerable < Solution . ReagentQuantity > reagents ,
bool setMaxVol = true ,
SolutionContainerManagerComponent ? solutionsMgr = null )
{
if ( ! Resolve ( uid , ref solutionsMgr , false ) )
solutionsMgr = EntityManager . EnsureComponent < SolutionContainerManagerComponent > ( uid ) ;
2023-01-19 03:56:45 +01:00
2023-01-12 16:41:40 +13:00
if ( ! solutionsMgr . Solutions . TryGetValue ( name , out var existing ) )
{
var newSolution = new Solution ( reagents , setMaxVol ) ;
solutionsMgr . Solutions . Add ( name , newSolution ) ;
return newSolution ;
}
existing . SetContents ( reagents , setMaxVol ) ;
return existing ;
}
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 ;
}
2023-04-10 15:37:03 +10:00
/// <summary>
/// Gets the most common reagent across all solutions by volume.
/// </summary>
/// <param name="component"></param>
public ReagentPrototype ? GetMaxReagent ( SolutionContainerManagerComponent component )
{
if ( component . Solutions . Count = = 0 )
return null ;
var reagentCounts = new Dictionary < string , FixedPoint2 > ( ) ;
foreach ( var solution in component . Solutions . Values )
{
foreach ( var reagent in solution . Contents )
{
reagentCounts . TryGetValue ( reagent . ReagentId , out var existing ) ;
existing + = reagent . Quantity ;
reagentCounts [ reagent . ReagentId ] = existing ;
}
}
var max = reagentCounts . Max ( ) ;
return _prototypeManager . Index < ReagentPrototype > ( max . Key ) ;
}
public SoundSpecifier ? GetSound ( SolutionContainerManagerComponent component )
{
var max = GetMaxReagent ( component ) ;
return max ? . FootstepSound ;
}
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 )
{
2023-01-12 16:41:40 +13:00
var heatCap = solution . GetHeatCapacity ( _prototypeManager ) ;
solution . Temperature = heatCap = = 0 ? 0 : thermalEnergy / heatCap ;
2022-02-18 03:42:39 +01:00
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
2023-01-12 16:41:40 +13:00
var heatCap = solution . GetHeatCapacity ( _prototypeManager ) ;
solution . Temperature + = heatCap = = 0 ? 0 : thermalEnergy / heatCap ;
2022-02-18 03:42:39 +01:00
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
}