Revert 'Revert 'Solution Entities'' (#23168)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
|
||||
namespace Content.Shared.Administration
|
||||
{
|
||||
@@ -8,9 +7,9 @@ namespace Content.Shared.Administration
|
||||
public sealed class EditSolutionsEuiState : EuiStateBase
|
||||
{
|
||||
public readonly NetEntity Target;
|
||||
public readonly Dictionary<string, Solution>? Solutions;
|
||||
public readonly List<(string, NetEntity)>? Solutions;
|
||||
|
||||
public EditSolutionsEuiState(NetEntity target, Dictionary<string, Solution>? solutions)
|
||||
public EditSolutionsEuiState(NetEntity target, List<(string, NetEntity)>? solutions)
|
||||
{
|
||||
Target = target;
|
||||
Solutions = solutions;
|
||||
|
||||
@@ -12,7 +12,6 @@ public sealed partial class DrainableSolutionComponent : Component
|
||||
/// <summary>
|
||||
/// Solution name that can be drained.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
|
||||
@@ -12,14 +12,12 @@ public sealed partial class DumpableSolutionComponent : Component
|
||||
/// <summary>
|
||||
/// Solution name that can be dumped into.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the solution can be dumped into infinitely.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("unlimited")]
|
||||
public bool Unlimited { get; set; } = false;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Unlimited = false;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components
|
||||
namespace Content.Shared.Chemistry.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the entity with this component to be placed in a <c>SharedReagentDispenserComponent</c>.
|
||||
/// <para>Otherwise it's considered to be too large or the improper shape to fit.</para>
|
||||
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
|
||||
/// since they can't be placed directly in them.</para>
|
||||
/// <see cref="Dispenser.SharedReagentDispenserComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent] // only needed for white-lists. Client doesn't actually need Solution data;
|
||||
public sealed partial class FitsInDispenserComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows the entity with this component to be placed in a <c>SharedReagentDispenserComponent</c>.
|
||||
/// <para>Otherwise it's considered to be too large or the improper shape to fit.</para>
|
||||
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
|
||||
/// since they can't be placed directly in them.</para>
|
||||
/// <see cref="Dispenser.SharedReagentDispenserComponent"/>
|
||||
/// Solution name that will interact with ReagentDispenserComponent.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent] // only needed for white-lists. Client doesn't actually need Solution data;
|
||||
public sealed partial class FitsInDispenserComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Solution name that will interact with ReagentDispenserComponent.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
}
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
|
||||
@@ -14,14 +14,12 @@ public sealed partial class RefillableSolutionComponent : Component
|
||||
/// <summary>
|
||||
/// Solution name that can added to easily.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount that can be transferred to the solution at once
|
||||
/// </summary>
|
||||
[DataField("maxRefill")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2? MaxRefill { get; set; } = null;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2? MaxRefill = null;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ public sealed partial class SmokeComponent : Component
|
||||
{
|
||||
public const string SolutionName = "solutionArea";
|
||||
|
||||
/// <summary>
|
||||
/// The solution on the entity with touch and ingestion reactions.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Entity<SolutionComponent>? Solution = null;
|
||||
|
||||
/// <summary>
|
||||
/// The max amount of tiles this smoke cloud can spread to.
|
||||
/// </summary>
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// <summary>
|
||||
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? Name;
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +101,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
foreach (var (reagent, quantity) in Contents)
|
||||
{
|
||||
_heatCapacity += (float) quantity *
|
||||
protoMan.Index<ReagentPrototype>(reagent.Prototype).SpecificHeat;
|
||||
protoMan.Index<ReagentPrototype>(reagent.Prototype).SpecificHeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,10 +159,12 @@ namespace Content.Shared.Chemistry.Components
|
||||
|
||||
public Solution(Solution solution)
|
||||
{
|
||||
Contents = solution.Contents.ShallowClone();
|
||||
Volume = solution.Volume;
|
||||
MaxVolume = solution.MaxVolume;
|
||||
Temperature = solution.Temperature;
|
||||
_heatCapacity = solution._heatCapacity;
|
||||
_heatCapacityDirty = solution._heatCapacityDirty;
|
||||
Contents = solution.Contents.ShallowClone();
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
@@ -174,7 +177,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
public void ValidateSolution()
|
||||
{
|
||||
// sandbox forbids: [Conditional("DEBUG")]
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
// Correct volume
|
||||
DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume);
|
||||
|
||||
@@ -192,7 +195,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
UpdateHeatCapacity(null);
|
||||
DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
|
||||
22
Content.Shared/Chemistry/Components/SolutionComponent.cs
Normal file
22
Content.Shared/Chemistry/Components/SolutionComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Holds the composition of an entity made from reagents and its reagent temperature.</para>
|
||||
/// <para>If the entity is used to represent a collection of reagents inside of a container such as a beaker, syringe, bloodstream, food, or similar the entity is tracked by a <see cref="SolutionContainerManagerComponent"/> on the container and has a <see cref="ContainedSolutionComponent"/> tracking which container it's in.</para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Once reagents and materials have been merged this component should be depricated in favor of using a combination of <see cref="PhysicalCompositionComponent"/> and <see cref="Content.Server.Temperature.Components.TemperatureComponent"/>. May require minor reworks to both.</para>
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class SolutionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>The reagents the entity is composed of and their temperature.</para>
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Solution Solution = new();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components.SolutionManager;
|
||||
|
||||
/// <summary>
|
||||
/// Component used to relate a solution to its container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When containers are finally ECS'd have this attach to the container entity.
|
||||
/// The <see cref="Solution.MaxVolume"/> field should then be extracted out into this component.
|
||||
/// Solution entities would just become an apporpriately composed entity hanging out in the container.
|
||||
/// Will probably require entities in components being given a relation to associate themselves with their container.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSolutionContainerSystem))]
|
||||
public sealed partial class ContainedSolutionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that the solution is contained in.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public EntityUid Container;
|
||||
|
||||
/// <summary>
|
||||
/// The name/key of the container the solution is located in.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public string ContainerName = default!;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ public sealed partial class DrawableSolutionComponent : Component
|
||||
/// <summary>
|
||||
/// Solution name that can be removed with syringes.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[RegisterComponent]
|
||||
public sealed partial class ExaminableSolutionComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ public sealed partial class InjectableSolutionComponent : Component
|
||||
/// <summary>
|
||||
/// Solution name which can be added with syringes.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("solution")]
|
||||
public string Solution { get; set; } = "default";
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Chemistry.Components.SolutionManager;
|
||||
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SolutionContainerSystem))]
|
||||
/// <summary>
|
||||
/// <para>A map of the solution entities contained within this entity.</para>
|
||||
/// <para>Every solution entity this maps should have a <see cref="SolutionComponent"/> to track its state and a <see cref="ContainedSolutionComponent"/> to track its container.</para>
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(SharedSolutionContainerSystem))]
|
||||
public sealed partial class SolutionContainerManagerComponent : Component
|
||||
{
|
||||
[DataField("solutions")]
|
||||
[Access(typeof(SolutionContainerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||
public Dictionary<string, Solution> Solutions = new();
|
||||
/// <summary>
|
||||
/// The default amount of space that will be allocated for solutions in solution containers.
|
||||
/// Most solution containers will only contain 1-2 solutions.
|
||||
/// </summary>
|
||||
public const int DefaultCapacity = 2;
|
||||
|
||||
/// <summary>
|
||||
/// The names of each solution container attached to this entity.
|
||||
/// Actually accessing them must be done via <see cref="ContainerManagerComponent"/>.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<string> Containers = new(DefaultCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// The set of solutions to load onto this entity during mapinit.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be null after mapinit.
|
||||
/// </remarks>
|
||||
[DataField(serverOnly: true)] // Needs to be serverOnly or these will get loaded on the client and never cleared. Can be reworked when entity spawning is predicted.
|
||||
public Dictionary<string, Solution>? Solutions = null;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract class SharedSolutionContainerMixerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solution = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
@@ -98,10 +98,10 @@ public abstract class SharedSolutionContainerMixerSystem : EntitySystem
|
||||
|
||||
foreach (var ent in container.ContainedEntities)
|
||||
{
|
||||
if (!_solution.TryGetFitsInDispenser(ent, out var solution))
|
||||
if (!_solution.TryGetFitsInDispenser(ent, out var soln, out _))
|
||||
continue;
|
||||
|
||||
_solution.UpdateChemicals(ent, solution, true, reactionMixer);
|
||||
_solution.UpdateChemicals(soln.Value, true, reactionMixer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
public abstract partial class SharedSolutionContainerSystem
|
||||
{
|
||||
#region Solution Accessors
|
||||
|
||||
public bool TryGetRefillableSolution(Entity<RefillableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetDrainableSolution(Entity<DrainableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetDumpableSolution(Entity<DumpableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetDrawableSolution(Entity<DrawableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetInjectableSolution(Entity<InjectableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetFitsInDispenser(Entity<FitsInDispenserComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
|
||||
{
|
||||
(soln, solution) = (default!, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
|
||||
}
|
||||
|
||||
public bool TryGetMixableSolution(Entity<SolutionContainerManagerComponent?> container, [NotNullWhen(true)] out Entity<SolutionComponent>? solution)
|
||||
{
|
||||
var getMixableSolutionAttempt = new GetMixableSolutionAttemptEvent(container);
|
||||
RaiseLocalEvent(container, ref getMixableSolutionAttempt);
|
||||
if (getMixableSolutionAttempt.MixedSolution != null)
|
||||
{
|
||||
solution = getMixableSolutionAttempt.MixedSolution;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Resolve(container, ref container.Comp, false))
|
||||
{
|
||||
solution = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tryGetSolution = EnumerateSolutions(container).FirstOrNull(x => x.Solution.Comp.Solution.CanMix);
|
||||
if (tryGetSolution.HasValue)
|
||||
{
|
||||
solution = tryGetSolution.Value.Solution;
|
||||
return true;
|
||||
}
|
||||
|
||||
solution = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion Solution Accessors
|
||||
|
||||
#region Solution Modifiers
|
||||
|
||||
public void Refill(Entity<RefillableSolutionComponent?> entity, Entity<SolutionComponent> soln, Solution refill)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
AddSolution(soln, refill);
|
||||
}
|
||||
|
||||
public void Inject(Entity<InjectableSolutionComponent?> entity, Entity<SolutionComponent> soln, Solution inject)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return;
|
||||
|
||||
AddSolution(soln, inject);
|
||||
}
|
||||
|
||||
public Solution Drain(Entity<DrainableSolutionComponent?> entity, Entity<SolutionComponent> soln, FixedPoint2 quantity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return new();
|
||||
|
||||
return SplitSolution(soln, quantity);
|
||||
}
|
||||
|
||||
public Solution Draw(Entity<DrawableSolutionComponent?> entity, Entity<SolutionComponent> soln, FixedPoint2 quantity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, logMissing: false))
|
||||
return new();
|
||||
|
||||
return SplitSolution(soln, quantity);
|
||||
}
|
||||
|
||||
#endregion Solution Modifiers
|
||||
|
||||
public float PercentFull(EntityUid uid)
|
||||
{
|
||||
if (!TryGetDrainableSolution(uid, out _, out var solution) || solution.MaxVolume.Equals(FixedPoint2.Zero))
|
||||
return 0;
|
||||
|
||||
return solution.FillFraction * 100;
|
||||
}
|
||||
|
||||
#region Static Methods
|
||||
|
||||
public static string ToPrettyString(Solution solution)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (solution.Name == null)
|
||||
sb.Append("[");
|
||||
else
|
||||
sb.Append($"{solution.Name}:[");
|
||||
var first = true;
|
||||
foreach (var (id, quantity) in solution.Contents)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.AppendFormat("{0}: {1}u", id, quantity);
|
||||
}
|
||||
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion Static Methods
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// This event alerts system that the solution was changed
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SolutionContainerChangedEvent
|
||||
{
|
||||
public readonly Solution Solution;
|
||||
public readonly string SolutionId;
|
||||
|
||||
public SolutionContainerChangedEvent(Solution solution, string solutionId)
|
||||
{
|
||||
SolutionId = solutionId;
|
||||
Solution = solution;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event raised when more reagents are added to a (managed) solution than it can hold.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SolutionContainerOverflowEvent(EntityUid SolutionEnt, Solution SolutionHolder, Solution Overflow)
|
||||
{
|
||||
/// <summary>The entity which contains the solution that has overflowed.</summary>
|
||||
public readonly EntityUid SolutionEnt = SolutionEnt;
|
||||
/// <summary>The solution that has overflowed.</summary>
|
||||
public readonly Solution SolutionHolder = SolutionHolder;
|
||||
/// <summary>The reagents that have overflowed the solution.</summary>
|
||||
public readonly Solution Overflow = Overflow;
|
||||
/// <summary>The volume by which the solution has overflowed.</summary>
|
||||
public readonly FixedPoint2 OverflowVol = Overflow.Volume;
|
||||
/// <summary>Whether some subscriber has taken care of the effects of the overflow.</summary>
|
||||
public bool Handled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ref event used to relay events raised on solution entities to their containers.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent"></typeparam>
|
||||
/// <param name="Event">The event that is being relayed.</param>
|
||||
/// <param name="ContainerEnt">The container entity that the event is being relayed to.</param>
|
||||
/// <param name="Name">The name of the solution entity that the event is being relayed from.</param>
|
||||
[ByRefEvent]
|
||||
public record struct SolutionRelayEvent<TEvent>(TEvent Event, EntityUid ContainerEnt, string Name)
|
||||
{
|
||||
public readonly EntityUid ContainerEnt = ContainerEnt;
|
||||
public readonly string Name = Name;
|
||||
public TEvent Event = Event;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ref event used to relay events raised on solution containers to their contained solutions.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent"></typeparam>
|
||||
/// <param name="Event">The event that is being relayed.</param>
|
||||
/// <param name="SolutionEnt">The solution entity that the event is being relayed to.</param>
|
||||
/// <param name="Name">The name of the solution entity that the event is being relayed to.</param>
|
||||
[ByRefEvent]
|
||||
public record struct SolutionContainerRelayEvent<TEvent>(TEvent Event, Entity<SolutionComponent> SolutionEnt, string Name)
|
||||
{
|
||||
public readonly Entity<SolutionComponent> SolutionEnt = SolutionEnt;
|
||||
public readonly string Name = Name;
|
||||
public TEvent Event = Event;
|
||||
}
|
||||
|
||||
#endregion Events
|
||||
|
||||
public abstract partial class SharedSolutionContainerSystem
|
||||
{
|
||||
protected void InitializeRelays()
|
||||
{
|
||||
SubscribeLocalEvent<ContainedSolutionComponent, SolutionChangedEvent>(OnSolutionChanged);
|
||||
SubscribeLocalEvent<ContainedSolutionComponent, SolutionOverflowEvent>(OnSolutionOverflow);
|
||||
SubscribeLocalEvent<ContainedSolutionComponent, ReactionAttemptEvent>(RelaySolutionRefEvent);
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
protected virtual void OnSolutionChanged(Entity<ContainedSolutionComponent> entity, ref SolutionChangedEvent args)
|
||||
{
|
||||
var (solutionId, solutionComp) = args.Solution;
|
||||
var solution = solutionComp.Solution;
|
||||
|
||||
UpdateAppearance(entity.Comp.Container, (solutionId, solutionComp, entity.Comp));
|
||||
|
||||
var relayEvent = new SolutionContainerChangedEvent(solution, entity.Comp.ContainerName);
|
||||
RaiseLocalEvent(entity.Comp.Container, ref relayEvent);
|
||||
}
|
||||
|
||||
protected virtual void OnSolutionOverflow(Entity<ContainedSolutionComponent> entity, ref SolutionOverflowEvent args)
|
||||
{
|
||||
var solution = args.Solution.Comp.Solution;
|
||||
var overflow = solution.SplitSolution(args.Overflow);
|
||||
var relayEv = new SolutionContainerOverflowEvent(entity.Owner, solution, overflow)
|
||||
{
|
||||
Handled = args.Handled,
|
||||
};
|
||||
|
||||
RaiseLocalEvent(entity.Comp.Container, ref relayEv);
|
||||
args.Handled = relayEv.Handled;
|
||||
}
|
||||
|
||||
#region Relay Event Handlers
|
||||
|
||||
private void RelaySolutionValEvent<TEvent>(EntityUid uid, ContainedSolutionComponent comp, TEvent @event)
|
||||
{
|
||||
var relayEvent = new SolutionRelayEvent<TEvent>(@event, uid, comp.ContainerName);
|
||||
RaiseLocalEvent(comp.Container, ref relayEvent);
|
||||
}
|
||||
|
||||
private void RelaySolutionRefEvent<TEvent>(Entity<ContainedSolutionComponent> entity, ref TEvent @event)
|
||||
{
|
||||
var relayEvent = new SolutionRelayEvent<TEvent>(@event, entity.Owner, entity.Comp.ContainerName);
|
||||
RaiseLocalEvent(entity.Comp.Container, ref relayEvent);
|
||||
@event = relayEvent.Event;
|
||||
}
|
||||
|
||||
private void RelaySolutionContainerEvent<TEvent>(EntityUid uid, SolutionContainerManagerComponent comp, TEvent @event)
|
||||
{
|
||||
foreach (var (name, soln) in EnumerateSolutions((uid, comp)))
|
||||
{
|
||||
var relayEvent = new SolutionContainerRelayEvent<TEvent>(@event, soln, name!);
|
||||
RaiseLocalEvent(soln, ref relayEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void RelaySolutionContainerEvent<TEvent>(Entity<SolutionContainerManagerComponent> entity, ref TEvent @event)
|
||||
{
|
||||
foreach (var (name, soln) in EnumerateSolutions((entity.Owner, entity.Comp)))
|
||||
{
|
||||
var relayEvent = new SolutionContainerRelayEvent<TEvent>(@event, soln, name!);
|
||||
RaiseLocalEvent(soln, ref relayEvent);
|
||||
@event = relayEvent.Event;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Relay Event Handlers
|
||||
|
||||
#endregion Event Handlers
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Dependency = Robust.Shared.IoC.DependencyAttribute;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// The event raised whenever a solution entity is modified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Raised after chemcial reactions and <see cref="SolutionOverflowEvent"/> are handled.
|
||||
/// </remarks>
|
||||
/// <param name="Solution">The solution entity that has been modified.</param>
|
||||
[ByRefEvent]
|
||||
public readonly partial record struct SolutionChangedEvent(Entity<SolutionComponent> Solution);
|
||||
|
||||
/// <summary>
|
||||
/// The event raised whenever a solution entity is filled past its capacity.
|
||||
/// </summary>
|
||||
/// <param name="Solution">The solution entity that has been overfilled.</param>
|
||||
/// <param name="Overflow">The amount by which the solution entity has been overfilled.</param>
|
||||
[ByRefEvent]
|
||||
public partial record struct SolutionOverflowEvent(Entity<SolutionComponent> Solution, FixedPoint2 Overflow)
|
||||
{
|
||||
/// <summary>The solution entity that has been overfilled.</summary>
|
||||
public readonly Entity<SolutionComponent> Solution = Solution;
|
||||
/// <summary>The amount by which the solution entity has been overfilled.</summary>
|
||||
public readonly FixedPoint2 Overflow = Overflow;
|
||||
/// <summary>Whether any of the event handlers for this event have handled overflow behaviour.</summary>
|
||||
public bool Handled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Part of Chemistry system deal with SolutionContainers
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
||||
[Dependency] protected readonly ChemicalReactionSystem ChemicalReactionSystem = default!;
|
||||
[Dependency] protected readonly ExamineSystemShared ExamineSystem = default!;
|
||||
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
InitializeRelays();
|
||||
|
||||
SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
|
||||
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnComponentInit);
|
||||
|
||||
SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
|
||||
SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve a solution associated with an entity.
|
||||
/// </summary>
|
||||
/// <param name="container">The entity that holdes the container the solution entity is in.</param>
|
||||
/// <param name="name">The name of the solution entities container.</param>
|
||||
/// <param name="entity">A reference to a solution entity to load the associated solution entity into. Will be unchanged if not null.</param>
|
||||
/// <param name="solution">Returns the solution state of the solution entity.</param>
|
||||
/// <returns>Whether the solution was successfully resolved.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!ResolveSolution(container, name, ref entity))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
solution = entity.Value.Comp.Solution;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ResolveSolution"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity)
|
||||
{
|
||||
if (entity is not null)
|
||||
{
|
||||
DebugTools.Assert(TryGetSolution(container, name, out var debugEnt)
|
||||
&& debugEnt.Value.Owner == entity.Value.Owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryGetSolution(container, name, out entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to fetch a solution entity associated with an entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the solution entity will be frequently accessed please use the equivalent <see cref="ResolveSolution"/> method and cache the result.
|
||||
/// </remarks>
|
||||
/// <param name="container">The entity the solution entity should be associated with.</param>
|
||||
/// <param name="name">The name of the solution entity to fetch.</param>
|
||||
/// <param name="entity">Returns the solution entity that was fetched.</param>
|
||||
/// <param name="solution">Returns the solution state of the solution entity that was fetched.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
|
||||
{
|
||||
if (!TryGetSolution(container, name, out entity))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
solution = entity.Value.Comp.Solution;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetSolution"/>
|
||||
public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity)
|
||||
{
|
||||
EntityUid uid;
|
||||
if (name is null)
|
||||
uid = container;
|
||||
else if (
|
||||
ContainerSystem.TryGetContainer(container, $"solution@{name}", out var solutionContainer) &&
|
||||
solutionContainer is ContainerSlot solutionSlot &&
|
||||
solutionSlot.ContainedEntity is { } containedSolution
|
||||
)
|
||||
uid = containedSolution;
|
||||
else
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryComp(uid, out SolutionComponent? comp))
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
entity = (uid, comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionContainerManagerComponent?> container, bool includeSelf = true)
|
||||
{
|
||||
if (includeSelf && TryComp(container, out SolutionComponent? solutionComp))
|
||||
yield return (null, (container.Owner, solutionComp));
|
||||
|
||||
if (!Resolve(container, ref container.Comp, logMissing: false))
|
||||
yield break;
|
||||
|
||||
foreach (var name in container.Comp.Containers)
|
||||
{
|
||||
if (ContainerSystem.GetContainer(container, $"solution@{name}") is ContainerSlot slot && slot.ContainedEntity is { } solutionId)
|
||||
yield return (name, (solutionId, Comp<SolutionComponent>(solutionId)));
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(string Name, Solution Solution)> EnumerateSolutions(SolutionContainerManagerComponent container)
|
||||
{
|
||||
if (container.Solutions is not { Count: > 0 } solutions)
|
||||
yield break;
|
||||
|
||||
foreach (var (name, solution) in solutions)
|
||||
{
|
||||
yield return (name, solution);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent, ContainedSolutionComponent> soln)
|
||||
{
|
||||
var (uid, appearanceComponent) = container;
|
||||
if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false))
|
||||
return;
|
||||
|
||||
var (_, comp, relation) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.SolutionName, relation.ContainerName, appearanceComponent);
|
||||
|
||||
if (solution.GetPrimaryReagentId() is { } reagent)
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
|
||||
else
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, string.Empty, appearanceComponent);
|
||||
}
|
||||
|
||||
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(EntityUid owner, string reagentId)
|
||||
{
|
||||
var reagentQuantity = FixedPoint2.New(0);
|
||||
if (EntityManager.EntityExists(owner)
|
||||
&& EntityManager.TryGetComponent(owner, out SolutionContainerManagerComponent? managerComponent))
|
||||
{
|
||||
foreach (var (_, soln) in EnumerateSolutions((owner, managerComponent)))
|
||||
{
|
||||
var solution = soln.Comp.Solution;
|
||||
reagentQuantity += solution.GetTotalPrototypeQuantity(reagentId);
|
||||
}
|
||||
}
|
||||
|
||||
return reagentQuantity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dirties a solution entity that has been modified and prompts updates to chemical reactions and overflow state.
|
||||
/// Should be invoked whenever a solution entity is modified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 90% of this system is ensuring that this proc is invoked whenever a solution entity is changed. The other 10% <i>is</i> this proc.
|
||||
/// </remarks>
|
||||
/// <param name="soln"></param>
|
||||
/// <param name="needsReactionsProcessing"></param>
|
||||
/// <param name="mixerComponent"></param>
|
||||
public void UpdateChemicals(Entity<SolutionComponent> soln, bool needsReactionsProcessing = true, ReactionMixerComponent? mixerComponent = null)
|
||||
{
|
||||
Dirty(soln);
|
||||
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
// Process reactions
|
||||
if (needsReactionsProcessing && solution.CanReact)
|
||||
ChemicalReactionSystem.FullyReactSolution(soln, mixerComponent);
|
||||
|
||||
var overflow = solution.Volume - solution.MaxVolume;
|
||||
if (overflow > FixedPoint2.Zero)
|
||||
{
|
||||
var overflowEv = new SolutionOverflowEvent(soln, overflow);
|
||||
RaiseLocalEvent(uid, ref overflowEv);
|
||||
}
|
||||
|
||||
UpdateAppearance((uid, comp, null));
|
||||
|
||||
var changedEv = new SolutionChangedEvent(soln);
|
||||
RaiseLocalEvent(uid, ref changedEv);
|
||||
}
|
||||
|
||||
public void UpdateAppearance(Entity<SolutionComponent, AppearanceComponent?> soln)
|
||||
{
|
||||
var (uid, comp, appearanceComponent) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (!EntityManager.EntityExists(uid) || !Resolve(uid, ref appearanceComponent, false))
|
||||
return;
|
||||
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
|
||||
|
||||
if (solution.GetPrimaryReagentId() is { } reagent)
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
|
||||
else
|
||||
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, string.Empty, appearanceComponent);
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, FixedPoint2 quantity)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var splitSol = solution.SplitSolution(quantity);
|
||||
UpdateChemicals(soln);
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
public Solution SplitStackSolution(Entity<SolutionComponent> soln, FixedPoint2 quantity, int stackCount)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var splitSol = solution.SplitSolution(quantity / stackCount);
|
||||
solution.SplitSolution(quantity - splitSol.Volume);
|
||||
UpdateChemicals(soln);
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a solution without the specified reagent(s).
|
||||
/// </summary>
|
||||
public Solution SplitSolutionWithout(Entity<SolutionComponent> soln, FixedPoint2 quantity, params string[] reagents)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var splitSol = solution.SplitSolutionWithout(quantity, reagents);
|
||||
UpdateChemicals(soln);
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
public void RemoveAllSolution(Entity<SolutionComponent> soln)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (solution.Volume == 0)
|
||||
return;
|
||||
|
||||
solution.RemoveAllSolution();
|
||||
UpdateChemicals(soln);
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, FixedPoint2 capacity)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (solution.MaxVolume == capacity)
|
||||
return;
|
||||
|
||||
solution.MaxVolume = capacity;
|
||||
UpdateChemicals(soln);
|
||||
}
|
||||
|
||||
/// <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="reagentQuantity">The 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(Entity<SolutionComponent> soln, ReagentQuantity reagentQuantity, out FixedPoint2 acceptedQuantity, float? temperature = null)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
acceptedQuantity = solution.AvailableVolume > reagentQuantity.Quantity
|
||||
? reagentQuantity.Quantity
|
||||
: solution.AvailableVolume;
|
||||
|
||||
if (acceptedQuantity <= 0)
|
||||
return reagentQuantity.Quantity == 0;
|
||||
|
||||
if (temperature == null)
|
||||
{
|
||||
solution.AddReagent(reagentQuantity.Reagent, acceptedQuantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
var proto = PrototypeManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
||||
solution.AddReagent(proto, acceptedQuantity, temperature.Value, PrototypeManager);
|
||||
}
|
||||
|
||||
UpdateChemicals(soln);
|
||||
return acceptedQuantity == reagentQuantity.Quantity;
|
||||
}
|
||||
|
||||
/// <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="prototype">The Id of the reagent to add.</param>
|
||||
/// <param name="quantity">The amount of reagent to add.</param>
|
||||
/// <returns>If all the reagent could be added.</returns>
|
||||
[PublicAPI]
|
||||
public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, float? temperature = null, ReagentData? data = null)
|
||||
=> TryAddReagent(soln, new ReagentQuantity(prototype, quantity, data), out _, temperature);
|
||||
|
||||
/// <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="prototype">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(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null, ReagentData? data = null)
|
||||
{
|
||||
var reagent = new ReagentQuantity(prototype, quantity, data);
|
||||
return TryAddReagent(soln, reagent, out acceptedQuantity, temperature);
|
||||
}
|
||||
|
||||
/// <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 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(Entity<SolutionComponent> soln, ReagentId reagentId, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null)
|
||||
{
|
||||
var quant = new ReagentQuantity(reagentId, quantity);
|
||||
return TryAddReagent(soln, quant, out acceptedQuantity, temperature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="reagentQuantity">The reagent to remove.</param>
|
||||
/// <returns>If the reagent to remove was found in the container.</returns>
|
||||
public bool RemoveReagent(Entity<SolutionComponent> soln, ReagentQuantity reagentQuantity)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var quant = solution.RemoveReagent(reagentQuantity);
|
||||
if (quant <= FixedPoint2.Zero)
|
||||
return false;
|
||||
|
||||
UpdateChemicals(soln);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="prototype">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 RemoveReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, ReagentData? data = null)
|
||||
{
|
||||
return RemoveReagent(soln, new ReagentQuantity(prototype, quantity, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="reagentId">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 RemoveReagent(Entity<SolutionComponent> soln, ReagentId reagentId, FixedPoint2 quantity)
|
||||
{
|
||||
return RemoveReagent(soln, new ReagentQuantity(reagentId, quantity));
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, Solution source, FixedPoint2 quantity)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (quantity < 0)
|
||||
throw new InvalidOperationException("Quantity must be positive");
|
||||
|
||||
quantity = FixedPoint2.Min(quantity, solution.AvailableVolume, source.Volume);
|
||||
if (quantity == 0)
|
||||
return false;
|
||||
|
||||
// TODO This should be made into a function that directly transfers reagents.
|
||||
// Currently this is quite inefficient.
|
||||
solution.AddSolution(source.SplitSolution(quantity), PrototypeManager);
|
||||
|
||||
UpdateChemicals(soln);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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="toAdd">solution being added</param>
|
||||
/// <returns>If the solution could be added.</returns>
|
||||
public bool TryAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return true;
|
||||
if (toAdd.Volume > solution.AvailableVolume)
|
||||
return false;
|
||||
|
||||
ForceAddSolution(soln, toAdd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds as much of a solution to a container as can fit.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <returns>The quantity of the solution actually added.</returns>
|
||||
public FixedPoint2 AddSolution(Entity<SolutionComponent> soln, Solution toAdd)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return FixedPoint2.Zero;
|
||||
|
||||
var quantity = FixedPoint2.Max(FixedPoint2.Zero, FixedPoint2.Min(toAdd.Volume, solution.AvailableVolume));
|
||||
if (quantity < toAdd.Volume)
|
||||
TryTransferSolution(soln, toAdd, quantity);
|
||||
else
|
||||
ForceAddSolution(soln, toAdd);
|
||||
|
||||
return quantity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a solution to a container and updates the container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <returns>Whether any reagents were added to the solution.</returns>
|
||||
public bool ForceAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return false;
|
||||
|
||||
solution.AddSolution(toAdd, PrototypeManager);
|
||||
UpdateChemicals(soln);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a solution to the container, removing the overflow.
|
||||
/// Unlike <see cref="TryAddSolution"/> it will ignore size limits.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <param name="overflowThreshold">The combined volume above which the overflow will be returned.
|
||||
/// If the combined volume is below this an empty solution is returned.</param>
|
||||
/// <param name="overflowingSolution">Solution that exceeded overflowThreshold</param>
|
||||
/// <returns>Whether any reagents were added to <paramref cref="targetSolution"/>.</returns>
|
||||
public bool TryMixAndOverflow(Entity<SolutionComponent> soln, Solution toAdd, FixedPoint2 overflowThreshold, [MaybeNullWhen(false)] out Solution overflowingSolution)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (toAdd.Volume == 0 || overflowThreshold > solution.MaxVolume)
|
||||
{
|
||||
overflowingSolution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
solution.AddSolution(toAdd, PrototypeManager);
|
||||
overflowingSolution = solution.SplitSolution(FixedPoint2.Max(FixedPoint2.Zero, solution.Volume - overflowThreshold));
|
||||
UpdateChemicals(soln);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, FixedPoint2 quantity)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (quantity <= 0)
|
||||
return new Solution();
|
||||
|
||||
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--)
|
||||
{
|
||||
var (reagent, _) = solution.Contents[i];
|
||||
var removedQuantity = solution.RemoveReagent(reagent, quantity);
|
||||
removedSolution.AddReagent(reagent, removedQuantity);
|
||||
}
|
||||
|
||||
UpdateChemicals(soln);
|
||||
return removedSolution;
|
||||
}
|
||||
|
||||
// Thermal energy and temperature management.
|
||||
|
||||
#region Thermal Energy and Temperature
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, float temperature)
|
||||
{
|
||||
var (_, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (temperature == solution.Temperature)
|
||||
return;
|
||||
|
||||
solution.Temperature = temperature;
|
||||
UpdateChemicals(soln);
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, float thermalEnergy)
|
||||
{
|
||||
var (_, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var heatCap = solution.GetHeatCapacity(PrototypeManager);
|
||||
solution.Temperature = heatCap == 0 ? 0 : thermalEnergy / heatCap;
|
||||
UpdateChemicals(soln);
|
||||
}
|
||||
|
||||
/// <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(Entity<SolutionComponent> soln, float thermalEnergy)
|
||||
{
|
||||
var (_, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
if (thermalEnergy == 0.0f)
|
||||
return;
|
||||
|
||||
var heatCap = solution.GetHeatCapacity(PrototypeManager);
|
||||
solution.Temperature += heatCap == 0 ? 0 : thermalEnergy / heatCap;
|
||||
UpdateChemicals(soln);
|
||||
}
|
||||
|
||||
#endregion Thermal Energy and Temperature
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnComponentInit(Entity<SolutionComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
entity.Comp.Solution.ValidateSolution();
|
||||
}
|
||||
|
||||
private void OnComponentStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
|
||||
{
|
||||
UpdateChemicals(entity);
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
RemoveAllSolution(entity);
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
if (entity.Comp.Containers is not { Count: > 0 } containers)
|
||||
return;
|
||||
|
||||
var containerManager = EnsureComp<ContainerManagerComponent>(entity);
|
||||
foreach (var name in containers)
|
||||
{
|
||||
// The actual solution entity should be directly held within the corresponding slot.
|
||||
ContainerSystem.EnsureContainer<ContainerSlot>(entity.Owner, $"solution@{name}", containerManager);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamineSolution(Entity<ExaminableSolutionComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
if (!TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var primaryReagent = solution.GetPrimaryReagentId();
|
||||
|
||||
if (string.IsNullOrEmpty(primaryReagent?.Prototype))
|
||||
{
|
||||
args.PushText(Loc.GetString("shared-solution-container-component-on-examine-empty-container"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PrototypeManager.TryIndex(primaryReagent.Value.Prototype, out ReagentPrototype? primary))
|
||||
{
|
||||
Log.Error($"{nameof(Solution)} could not find the prototype associated with {primaryReagent}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var colorHex = solution.GetColor(PrototypeManager)
|
||||
.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";
|
||||
|
||||
args.PushMarkup(Loc.GetString(messageString,
|
||||
("color", colorHex),
|
||||
("wordedAmount", Loc.GetString(solution.Contents.Count == 1
|
||||
? "shared-solution-container-component-on-examine-worded-amount-one-reagent"
|
||||
: "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")),
|
||||
("desc", primary.LocalizedPhysicalDescription)));
|
||||
|
||||
var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager);
|
||||
|
||||
// Sort the reagents by amount, descending then alphabetically
|
||||
var sortedReagentPrototypes = reagentPrototypes
|
||||
.OrderByDescending(pair => pair.Value.Value)
|
||||
.ThenBy(pair => pair.Key.LocalizedName);
|
||||
|
||||
// Add descriptions of immediately recognizable reagents, like water or beer
|
||||
var recognized = new List<ReagentPrototype>();
|
||||
foreach (var keyValuePair in sortedReagentPrototypes)
|
||||
{
|
||||
var proto = keyValuePair.Key;
|
||||
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())));
|
||||
}
|
||||
|
||||
private void OnSolutionExaminableVerb(Entity<ExaminableSolutionComponent> entity, ref GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var scanEvent = new SolutionScanEvent();
|
||||
RaiseLocalEvent(args.User, scanEvent);
|
||||
if (!scanEvent.CanScan)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetSolution(args.Target, entity.Comp.Solution, out _, out var solutionHolder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var target = args.Target;
|
||||
var user = args.User;
|
||||
var verb = new ExamineVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
var markup = GetSolutionExamine(solutionHolder);
|
||||
ExamineSystem.SendExamineTooltip(user, target, 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.Volume == 0)
|
||||
{
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-empty-container"));
|
||||
return msg;
|
||||
}
|
||||
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-main-text"));
|
||||
|
||||
var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager);
|
||||
|
||||
// Sort the reagents by amount, descending then alphabetically
|
||||
var sortedReagentPrototypes = reagentPrototypes
|
||||
.OrderByDescending(pair => pair.Value.Value)
|
||||
.ThenBy(pair => pair.Key.LocalizedName);
|
||||
|
||||
foreach (var (proto, quantity) in sortedReagentPrototypes)
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-chemical"
|
||||
, ("type", proto.LocalizedName)
|
||||
, ("color", proto.SubstanceColor.ToHexNoAlpha())
|
||||
, ("amount", quantity)));
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
#endregion Event Handlers
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
public sealed partial class SolutionContainerSystem
|
||||
{
|
||||
public void Refill(EntityUid targetUid, Solution targetSolution, Solution addedSolution,
|
||||
RefillableSolutionComponent? refillableSolution = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref refillableSolution, false))
|
||||
return;
|
||||
|
||||
TryAddSolution(targetUid, targetSolution, addedSolution);
|
||||
}
|
||||
|
||||
public void Inject(EntityUid targetUid, Solution targetSolution, Solution addedSolution,
|
||||
InjectableSolutionComponent? injectableSolution = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref injectableSolution, false))
|
||||
return;
|
||||
|
||||
TryAddSolution(targetUid, targetSolution, addedSolution);
|
||||
}
|
||||
|
||||
public Solution Draw(EntityUid targetUid, Solution solution, FixedPoint2 amount,
|
||||
DrawableSolutionComponent? drawableSolution = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref drawableSolution, false))
|
||||
return new Solution();
|
||||
|
||||
return SplitSolution(targetUid, solution, amount);
|
||||
}
|
||||
|
||||
public Solution Drain(EntityUid targetUid, Solution targetSolution, FixedPoint2 amount,
|
||||
DrainableSolutionComponent? drainableSolution = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref drainableSolution, false))
|
||||
return new Solution();
|
||||
|
||||
return SplitSolution(targetUid, targetSolution, amount);
|
||||
}
|
||||
|
||||
public bool TryGetInjectableSolution(EntityUid targetUid,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
InjectableSolutionComponent? injectable = null,
|
||||
SolutionContainerManagerComponent? manager = null
|
||||
)
|
||||
{
|
||||
if (!Resolve(targetUid, ref manager, ref injectable, false)
|
||||
|| !manager.Solutions.TryGetValue(injectable.Solution, out solution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetRefillableSolution(EntityUid targetUid,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
SolutionContainerManagerComponent? solutionManager = null,
|
||||
RefillableSolutionComponent? refillable = null)
|
||||
{
|
||||
if (!Resolve(targetUid, ref solutionManager, ref refillable, false)
|
||||
|| !solutionManager.Solutions.TryGetValue(refillable.Solution, out var refillableSolution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
solution = refillableSolution;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetDrainableSolution(EntityUid uid,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
DrainableSolutionComponent? drainable = null,
|
||||
SolutionContainerManagerComponent? manager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref drainable, ref manager, false)
|
||||
|| !manager.Solutions.TryGetValue(drainable.Solution, out solution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetDumpableSolution(EntityUid uid,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
DumpableSolutionComponent? dumpable = null,
|
||||
SolutionContainerManagerComponent? manager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref dumpable, ref manager, false)
|
||||
|| !manager.Solutions.TryGetValue(dumpable.Solution, out solution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetDrawableSolution(EntityUid uid,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
DrawableSolutionComponent? drawable = null,
|
||||
SolutionContainerManagerComponent? manager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref drawable, ref manager, false)
|
||||
|| !manager.Solutions.TryGetValue(drawable.Solution, out solution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public FixedPoint2 DrainAvailable(EntityUid uid)
|
||||
{
|
||||
return !TryGetDrainableSolution(uid, out var solution)
|
||||
? FixedPoint2.Zero
|
||||
: solution.Volume;
|
||||
}
|
||||
|
||||
public float PercentFull(EntityUid uid)
|
||||
{
|
||||
if (!TryGetDrainableSolution(uid, out var solution) || solution.MaxVolume.Equals(FixedPoint2.Zero))
|
||||
return 0;
|
||||
|
||||
return solution.FillFraction * 100;
|
||||
}
|
||||
|
||||
public bool TryGetFitsInDispenser(EntityUid owner,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
FitsInDispenserComponent? dispenserFits = null,
|
||||
SolutionContainerManagerComponent? solutionManager = null)
|
||||
{
|
||||
if (!Resolve(owner, ref dispenserFits, ref solutionManager, false)
|
||||
|| !solutionManager.Solutions.TryGetValue(dispenserFits.Solution, out solution))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string ToPrettyString(Solution solution)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (solution.Name == null)
|
||||
sb.Append("[");
|
||||
else
|
||||
sb.Append($"{solution.Name}:[");
|
||||
var first = true;
|
||||
foreach (var (id, quantity) in solution.Contents)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.AppendFormat("{0}: {1}u", id, quantity);
|
||||
}
|
||||
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,871 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This event alerts system that the solution was changed
|
||||
/// </summary>
|
||||
public sealed class SolutionChangedEvent : EntityEventArgs
|
||||
{
|
||||
public readonly Solution Solution;
|
||||
public readonly string SolutionId;
|
||||
|
||||
public SolutionChangedEvent(Solution solution, string solutionId)
|
||||
{
|
||||
SolutionId = solutionId;
|
||||
Solution = solution;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event raised when more reagents are added to a (managed) solution than it can hold.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SolutionOverflowEvent(EntityUid SolutionEnt, Solution SolutionHolder, Solution Overflow)
|
||||
{
|
||||
/// <summary>The entity which contains the solution that has overflowed.</summary>
|
||||
public readonly EntityUid SolutionEnt = SolutionEnt;
|
||||
/// <summary>The solution that has overflowed.</summary>
|
||||
public readonly Solution SolutionHolder = SolutionHolder;
|
||||
/// <summary>The reagents that have overflowed the solution.</summary>
|
||||
public readonly Solution Overflow = Overflow;
|
||||
/// <summary>The volume by which the solution has overflowed.</summary>
|
||||
public readonly FixedPoint2 OverflowVol = Overflow.Volume;
|
||||
/// <summary>Whether some subscriber has taken care of the effects of the overflow.</summary>
|
||||
public bool Handled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Part of Chemistry system deal with SolutionContainers
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class SolutionContainerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ChemicalReactionSystem _chemistrySystem = default!;
|
||||
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(InitSolution);
|
||||
SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
|
||||
SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
|
||||
}
|
||||
|
||||
private void InitSolution(EntityUid uid, SolutionContainerManagerComponent component, ComponentInit args)
|
||||
{
|
||||
foreach (var (name, solutionHolder) in component.Solutions)
|
||||
{
|
||||
solutionHolder.Name = name;
|
||||
solutionHolder.ValidateSolution();
|
||||
UpdateAppearance(uid, solutionHolder);
|
||||
}
|
||||
}
|
||||
|
||||
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.Volume == 0)
|
||||
{
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-empty-container"));
|
||||
return msg;
|
||||
}
|
||||
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-main-text"));
|
||||
|
||||
foreach (var (proto, quantity) in solution.GetReagentPrototypes(_prototypeManager))
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddMarkup(Loc.GetString("scannable-solution-chemical"
|
||||
, ("type", proto.LocalizedName)
|
||||
, ("color", proto.SubstanceColor.ToHexNoAlpha())
|
||||
, ("amount", quantity)));
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
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 solution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var primaryReagent = solution.GetPrimaryReagentId();
|
||||
|
||||
if (string.IsNullOrEmpty(primaryReagent?.Prototype))
|
||||
{
|
||||
args.PushText(Loc.GetString("shared-solution-container-component-on-examine-empty-container"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(primaryReagent.Value.Prototype, out ReagentPrototype? primary))
|
||||
{
|
||||
Log.Error($"{nameof(Solution)} could not find the prototype associated with {primaryReagent}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var colorHex = solution.GetColor(_prototypeManager)
|
||||
.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";
|
||||
|
||||
args.PushMarkup(Loc.GetString(messageString,
|
||||
("color", colorHex),
|
||||
("wordedAmount", Loc.GetString(solution.Contents.Count == 1
|
||||
? "shared-solution-container-component-on-examine-worded-amount-one-reagent"
|
||||
: "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")),
|
||||
("desc", primary.LocalizedPhysicalDescription)));
|
||||
|
||||
|
||||
var reagentPrototypes = solution.GetReagentPrototypes(_prototypeManager);
|
||||
|
||||
// Sort the reagents by amount, descending then alphabetically
|
||||
var sortedReagentPrototypes = reagentPrototypes
|
||||
.OrderByDescending(pair => pair.Value.Value)
|
||||
.ThenBy(pair => pair.Key.LocalizedName);
|
||||
|
||||
// Add descriptions of immediately recognizable reagents, like water or beer
|
||||
var recognized = new List<ReagentPrototype>();
|
||||
foreach (var keyValuePair in sortedReagentPrototypes)
|
||||
{
|
||||
var proto = keyValuePair.Key;
|
||||
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())));
|
||||
}
|
||||
|
||||
public void UpdateAppearance(EntityUid uid, Solution solution,
|
||||
AppearanceComponent? appearanceComponent = null)
|
||||
{
|
||||
if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, false))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
|
||||
_appearance.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(_prototypeManager), appearanceComponent);
|
||||
if (solution.Name != null)
|
||||
{
|
||||
_appearance.SetData(uid, SolutionContainerVisuals.SolutionName, solution.Name, appearanceComponent);
|
||||
}
|
||||
|
||||
if (solution.GetPrimaryReagentId() is { } reagent)
|
||||
{
|
||||
_appearance.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appearance.SetData(uid, SolutionContainerVisuals.BaseOverride, string.Empty, appearanceComponent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
public Solution SplitStackSolution(EntityUid targetUid, Solution solutionHolder, FixedPoint2 quantity, int stackCount)
|
||||
{
|
||||
var splitSol = solutionHolder.SplitSolution(quantity / stackCount);
|
||||
solutionHolder.SplitSolution(quantity - splitSol.Volume);
|
||||
UpdateChemicals(targetUid, solutionHolder);
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits a solution without the specified reagent(s).
|
||||
/// </summary>
|
||||
public Solution SplitSolutionWithout(EntityUid targetUid, Solution solutionHolder, FixedPoint2 quantity,
|
||||
params string[] reagents)
|
||||
{
|
||||
var splitSol = solutionHolder.SplitSolutionWithout(quantity, reagents);
|
||||
UpdateChemicals(targetUid, solutionHolder);
|
||||
return splitSol;
|
||||
}
|
||||
|
||||
public void UpdateChemicals(EntityUid uid, Solution solutionHolder, bool needsReactionsProcessing = false, ReactionMixerComponent? mixerComponent = null)
|
||||
{
|
||||
DebugTools.Assert(solutionHolder.Name != null && TryGetSolution(uid, solutionHolder.Name, out var tmp) && tmp == solutionHolder);
|
||||
|
||||
// Process reactions
|
||||
if (needsReactionsProcessing && solutionHolder.CanReact)
|
||||
{
|
||||
_chemistrySystem.FullyReactSolution(solutionHolder, uid, solutionHolder.MaxVolume, mixerComponent);
|
||||
}
|
||||
|
||||
var overflowVol = solutionHolder.Volume - solutionHolder.MaxVolume;
|
||||
if (overflowVol > FixedPoint2.Zero)
|
||||
{
|
||||
var overflow = solutionHolder.SplitSolution(overflowVol);
|
||||
var overflowEv = new SolutionOverflowEvent(uid, solutionHolder, overflow);
|
||||
RaiseLocalEvent(uid, ref overflowEv);
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, solutionHolder);
|
||||
RaiseLocalEvent(uid, new SolutionChangedEvent(solutionHolder, solutionHolder.Name));
|
||||
}
|
||||
|
||||
public void RemoveAllSolution(EntityUid uid, Solution solutionHolder)
|
||||
{
|
||||
if (solutionHolder.Volume == 0)
|
||||
return;
|
||||
|
||||
solutionHolder.RemoveAllSolution();
|
||||
UpdateChemicals(uid, solutionHolder);
|
||||
}
|
||||
|
||||
public void RemoveAllSolution(EntityUid uid, SolutionContainerManagerComponent? solutionContainerManager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref solutionContainerManager))
|
||||
return;
|
||||
|
||||
foreach (var solution in solutionContainerManager.Solutions.Values)
|
||||
{
|
||||
RemoveAllSolution(uid, solution);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
targetSolution.MaxVolume = capacity;
|
||||
if (capacity < targetSolution.Volume)
|
||||
targetSolution.RemoveSolution(targetSolution.Volume - capacity);
|
||||
|
||||
UpdateChemicals(targetUid, targetSolution);
|
||||
}
|
||||
|
||||
/// <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="reagentQuantity">The 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, ReagentQuantity reagentQuantity,
|
||||
out FixedPoint2 acceptedQuantity, float? temperature = null)
|
||||
{
|
||||
acceptedQuantity = targetSolution.AvailableVolume > reagentQuantity.Quantity
|
||||
? reagentQuantity.Quantity
|
||||
: targetSolution.AvailableVolume;
|
||||
|
||||
if (acceptedQuantity <= 0)
|
||||
return reagentQuantity.Quantity == 0;
|
||||
|
||||
if (temperature == null)
|
||||
{
|
||||
targetSolution.AddReagent(reagentQuantity.Reagent, acceptedQuantity);
|
||||
}
|
||||
else
|
||||
{
|
||||
var proto = _prototypeManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
||||
targetSolution.AddReagent(proto, acceptedQuantity, temperature.Value, _prototypeManager);
|
||||
}
|
||||
|
||||
UpdateChemicals(targetUid, targetSolution, true);
|
||||
return acceptedQuantity == reagentQuantity.Quantity;
|
||||
}
|
||||
|
||||
/// <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="prototype">The Id of the reagent to add.</param>
|
||||
/// <param name="quantity">The amount of reagent to add.</param>
|
||||
/// <returns>If all the reagent could be added.</returns>
|
||||
[PublicAPI]
|
||||
public bool TryAddReagent(EntityUid targetUid, Solution targetSolution, string prototype, FixedPoint2 quantity,
|
||||
float? temperature = null, ReagentData? data = null)
|
||||
{
|
||||
var reagent = new ReagentQuantity(prototype, quantity, data);
|
||||
return TryAddReagent(targetUid, targetSolution, reagent, out _, temperature);
|
||||
}
|
||||
|
||||
/// <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="prototype">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 prototype, FixedPoint2 quantity,
|
||||
out FixedPoint2 acceptedQuantity, float? temperature = null, ReagentData? data = null)
|
||||
{
|
||||
var reagent = new ReagentQuantity(prototype, quantity, data);
|
||||
return TryAddReagent(targetUid, targetSolution, reagent, out acceptedQuantity, temperature);
|
||||
}
|
||||
|
||||
/// <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 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, ReagentId reagentId, FixedPoint2 quantity,
|
||||
out FixedPoint2 acceptedQuantity, float? temperature = null)
|
||||
{
|
||||
var quant = new ReagentQuantity(reagentId, quantity);
|
||||
return TryAddReagent(targetUid, targetSolution, quant, out acceptedQuantity, temperature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="reagentQuantity">The reagent to remove.</param>
|
||||
/// <returns>If the reagent to remove was found in the container.</returns>
|
||||
public bool RemoveReagent(EntityUid targetUid, Solution? container, ReagentQuantity reagentQuantity)
|
||||
{
|
||||
if (container == null)
|
||||
return false;
|
||||
|
||||
var quant = container.RemoveReagent(reagentQuantity);
|
||||
if (quant <= FixedPoint2.Zero)
|
||||
return false;
|
||||
|
||||
UpdateChemicals(targetUid, container);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="prototype">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 RemoveReagent(EntityUid targetUid, Solution? container, string prototype, FixedPoint2 quantity, ReagentData? data = null)
|
||||
{
|
||||
return RemoveReagent(targetUid, container, new ReagentQuantity(prototype, quantity, data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes reagent from a container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid"></param>
|
||||
/// <param name="container">Solution container from which we are removing reagent</param>
|
||||
/// <param name="reagentId">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 RemoveReagent(EntityUid targetUid, Solution? container, ReagentId reagentId, FixedPoint2 quantity)
|
||||
{
|
||||
return RemoveReagent(targetUid, container, new ReagentQuantity(reagentId, quantity));
|
||||
}
|
||||
|
||||
/// <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 (!TryTransferSolution(targetUid, target, source, quantity))
|
||||
return false;
|
||||
|
||||
UpdateChemicals(sourceUid, source, false);
|
||||
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 targetUid, Solution target, Solution source, FixedPoint2 quantity)
|
||||
{
|
||||
if (quantity < 0)
|
||||
throw new InvalidOperationException("Quantity must be positive");
|
||||
|
||||
quantity = FixedPoint2.Min(quantity, target.AvailableVolume, source.Volume);
|
||||
if (quantity == 0)
|
||||
return false;
|
||||
|
||||
// TODO This should be made into a function that directly transfers reagents.
|
||||
// Currently this is quite inefficient.
|
||||
target.AddSolution(source.SplitSolution(quantity), _prototypeManager);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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="toAdd">solution being added</param>
|
||||
/// <returns>If the solution could be added.</returns>
|
||||
public bool TryAddSolution(EntityUid targetUid, Solution targetSolution, Solution toAdd)
|
||||
{
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return true;
|
||||
if (toAdd.Volume > targetSolution.AvailableVolume)
|
||||
return false;
|
||||
|
||||
ForceAddSolution(targetUid, targetSolution, toAdd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds as much of a solution to a container as can fit.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <returns>The quantity of the solution actually added.</returns>
|
||||
public FixedPoint2 AddSolution(EntityUid targetUid, Solution targetSolution, Solution toAdd)
|
||||
{
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return FixedPoint2.Zero;
|
||||
|
||||
var quantity = FixedPoint2.Max(FixedPoint2.Zero, FixedPoint2.Min(toAdd.Volume, targetSolution.AvailableVolume));
|
||||
if (quantity < toAdd.Volume)
|
||||
TryTransferSolution(targetUid, targetSolution, toAdd, quantity);
|
||||
else
|
||||
ForceAddSolution(targetUid, targetSolution, toAdd);
|
||||
|
||||
return quantity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a solution to a container and updates the container.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <returns>Whether any reagents were added to the solution.</returns>
|
||||
public bool ForceAddSolution(EntityUid targetUid, Solution targetSolution, Solution toAdd)
|
||||
{
|
||||
if (toAdd.Volume == FixedPoint2.Zero)
|
||||
return false;
|
||||
|
||||
targetSolution.AddSolution(toAdd, _prototypeManager);
|
||||
UpdateChemicals(targetUid, targetSolution, needsReactionsProcessing: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a solution to the container, removing the overflow.
|
||||
/// Unlike <see cref="TryAddSolution"/> it will ignore size limits.
|
||||
/// </summary>
|
||||
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
|
||||
/// <param name="targetSolution">The solution being added to.</param>
|
||||
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
|
||||
/// <param name="overflowThreshold">The combined volume above which the overflow will be returned.
|
||||
/// If the combined volume is below this an empty solution is returned.</param>
|
||||
/// <param name="overflowingSolution">Solution that exceeded overflowThreshold</param>
|
||||
/// <returns>Whether any reagents were added to <paramref cref="targetSolution"/>.</returns>
|
||||
public bool TryMixAndOverflow(EntityUid targetUid, Solution targetSolution,
|
||||
Solution toAdd,
|
||||
FixedPoint2 overflowThreshold,
|
||||
[NotNullWhen(true)] out Solution? overflowingSolution)
|
||||
{
|
||||
if (toAdd.Volume == 0 || overflowThreshold > targetSolution.MaxVolume)
|
||||
{
|
||||
overflowingSolution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
targetSolution.AddSolution(toAdd, _prototypeManager);
|
||||
overflowingSolution = targetSolution.SplitSolution(FixedPoint2.Max(FixedPoint2.Zero, targetSolution.Volume - overflowThreshold));
|
||||
UpdateChemicals(targetUid, targetSolution, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetSolution([NotNullWhen(true)] EntityUid? uid, string name,
|
||||
[NotNullWhen(true)] out Solution? solution,
|
||||
SolutionContainerManagerComponent? solutionsMgr = null)
|
||||
{
|
||||
if (uid == null || !Resolve(uid.Value, ref solutionsMgr, false))
|
||||
{
|
||||
solution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <param name="existed">true if the solution already existed</param>
|
||||
/// <returns>solution</returns>
|
||||
public Solution EnsureSolution(EntityUid uid, string name, 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;
|
||||
return newSolution;
|
||||
}
|
||||
|
||||
existed = true;
|
||||
return existing;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="minVol">Ensures that the solution's maximum volume is larger than this value.</param>
|
||||
/// <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<ReagentQuantity> reagents,
|
||||
bool setMaxVol = true,
|
||||
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(reagents, setMaxVol);
|
||||
solutionsMgr.Solutions.Add(name, newSolution);
|
||||
return newSolution;
|
||||
}
|
||||
|
||||
existing.SetContents(reagents, setMaxVol);
|
||||
return existing;
|
||||
}
|
||||
/// <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)
|
||||
{
|
||||
if (quantity <= 0)
|
||||
return new Solution();
|
||||
|
||||
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--)
|
||||
{
|
||||
var (reagent, _) = solution.Contents[i];
|
||||
var removedQuantity = solution.RemoveReagent(reagent, quantity);
|
||||
removedSolution.AddReagent(reagent, removedQuantity);
|
||||
}
|
||||
|
||||
UpdateChemicals(uid, solution);
|
||||
return removedSolution;
|
||||
}
|
||||
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(EntityUid owner, string reagentId)
|
||||
{
|
||||
var reagentQuantity = FixedPoint2.New(0);
|
||||
if (EntityManager.EntityExists(owner)
|
||||
&& EntityManager.TryGetComponent(owner, out SolutionContainerManagerComponent? managerComponent))
|
||||
{
|
||||
foreach (var solution in managerComponent.Solutions.Values)
|
||||
{
|
||||
reagentQuantity += solution.GetTotalPrototypeQuantity(reagentId);
|
||||
}
|
||||
}
|
||||
|
||||
return reagentQuantity;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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<ReagentId, FixedPoint2>();
|
||||
|
||||
foreach (var solution in component.Solutions.Values)
|
||||
{
|
||||
foreach (var (reagent, quantity) in solution.Contents)
|
||||
{
|
||||
reagentCounts.TryGetValue(reagent, out var existing);
|
||||
existing += quantity;
|
||||
reagentCounts[reagent] = existing;
|
||||
}
|
||||
}
|
||||
|
||||
var max = reagentCounts.Max();
|
||||
|
||||
return _prototypeManager.Index<ReagentPrototype>(max.Key.Prototype);
|
||||
}
|
||||
|
||||
public SoundSpecifier? GetSound(SolutionContainerManagerComponent component)
|
||||
{
|
||||
var max = GetMaxReagent(component);
|
||||
return max?.FootstepSound;
|
||||
}
|
||||
|
||||
// Thermal energy and temperature management.
|
||||
|
||||
#region Thermal Energy and Temperature
|
||||
|
||||
/// <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;
|
||||
|
||||
solution.Temperature = temperature;
|
||||
UpdateChemicals(owner, solution, true);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
var heatCap = solution.GetHeatCapacity(_prototypeManager);
|
||||
solution.Temperature = heatCap == 0 ? 0 : thermalEnergy / heatCap;
|
||||
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;
|
||||
|
||||
var heatCap = solution.GetHeatCapacity(_prototypeManager);
|
||||
solution.Temperature += heatCap == 0 ? 0 : thermalEnergy / heatCap;
|
||||
UpdateChemicals(owner, solution, true);
|
||||
}
|
||||
|
||||
#endregion Thermal Energy and Temperature
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
#endregion Event Handlers
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Chemistry.Reaction
|
||||
{
|
||||
@@ -49,7 +48,7 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
{
|
||||
// Construct single-reaction dictionary.
|
||||
var dict = new Dictionary<string, List<ReactionPrototype>>();
|
||||
foreach(var reaction in _prototypeManager.EnumeratePrototypes<ReactionPrototype>())
|
||||
foreach (var reaction in _prototypeManager.EnumeratePrototypes<ReactionPrototype>())
|
||||
{
|
||||
// For this dictionary we only need to cache based on the first reagent.
|
||||
var reagent = reaction.Reactants.Keys.First();
|
||||
@@ -59,7 +58,7 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
_reactionsSingle = dict.ToFrozenDictionary();
|
||||
|
||||
dict.Clear();
|
||||
foreach(var reaction in _prototypeManager.EnumeratePrototypes<ReactionPrototype>())
|
||||
foreach (var reaction in _prototypeManager.EnumeratePrototypes<ReactionPrototype>())
|
||||
{
|
||||
foreach (var reagent in reaction.Reactants.Keys)
|
||||
{
|
||||
@@ -87,8 +86,10 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
/// <param name="reaction">The reaction to check.</param>
|
||||
/// <param name="lowestUnitReactions">How many times this reaction can occur.</param>
|
||||
/// <returns></returns>
|
||||
private bool CanReact(Solution solution, ReactionPrototype reaction, EntityUid owner, ReactionMixerComponent? mixerComponent, out FixedPoint2 lowestUnitReactions)
|
||||
private bool CanReact(Entity<SolutionComponent> soln, ReactionPrototype reaction, ReactionMixerComponent? mixerComponent, out FixedPoint2 lowestUnitReactions)
|
||||
{
|
||||
var solution = soln.Comp.Solution;
|
||||
|
||||
lowestUnitReactions = FixedPoint2.MaxValue;
|
||||
if (solution.Temperature < reaction.MinimumTemperature)
|
||||
{
|
||||
@@ -101,15 +102,15 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
return false;
|
||||
}
|
||||
|
||||
if((mixerComponent == null && reaction.MixingCategories != null) ||
|
||||
if ((mixerComponent == null && reaction.MixingCategories != null) ||
|
||||
mixerComponent != null && reaction.MixingCategories != null && reaction.MixingCategories.Except(mixerComponent.ReactionTypes).Any())
|
||||
{
|
||||
lowestUnitReactions = FixedPoint2.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
var attempt = new ReactionAttemptEvent(reaction, solution);
|
||||
RaiseLocalEvent(owner, attempt);
|
||||
var attempt = new ReactionAttemptEvent(reaction, soln);
|
||||
RaiseLocalEvent(soln, ref attempt);
|
||||
if (attempt.Cancelled)
|
||||
{
|
||||
lowestUnitReactions = FixedPoint2.Zero;
|
||||
@@ -155,8 +156,11 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
/// Perform a reaction on a solution. This assumes all reaction criteria are met.
|
||||
/// Removes the reactants from the solution, adds products, and returns a list of products.
|
||||
/// </summary>
|
||||
private List<string> PerformReaction(Solution solution, EntityUid owner, ReactionPrototype reaction, FixedPoint2 unitReactions)
|
||||
private List<string> PerformReaction(Entity<SolutionComponent> soln, ReactionPrototype reaction, FixedPoint2 unitReactions)
|
||||
{
|
||||
var (uid, comp) = soln;
|
||||
var solution = comp.Solution;
|
||||
|
||||
var energy = reaction.ConserveEnergy ? solution.GetThermalEnergy(_prototypeManager) : 0;
|
||||
|
||||
//Remove reactants
|
||||
@@ -184,20 +188,20 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
solution.Temperature = energy / newCap;
|
||||
}
|
||||
|
||||
OnReaction(solution, reaction, null, owner, unitReactions);
|
||||
OnReaction(soln, reaction, null, unitReactions);
|
||||
|
||||
return products;
|
||||
}
|
||||
|
||||
private void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype? reagent, EntityUid owner, FixedPoint2 unitReactions)
|
||||
private void OnReaction(Entity<SolutionComponent> soln, ReactionPrototype reaction, ReagentPrototype? reagent, FixedPoint2 unitReactions)
|
||||
{
|
||||
var args = new ReagentEffectArgs(owner, null, solution,
|
||||
var args = new ReagentEffectArgs(soln, null, soln.Comp.Solution,
|
||||
reagent,
|
||||
unitReactions, EntityManager, null, 1f);
|
||||
|
||||
var coordinates = Transform(owner).Coordinates;
|
||||
var coordinates = Transform(soln).Coordinates;
|
||||
_adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
|
||||
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(owner):metabolizer} at {coordinates}");
|
||||
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(soln):metabolizer} at {coordinates}");
|
||||
|
||||
foreach (var effect in reaction.Effects)
|
||||
{
|
||||
@@ -214,7 +218,7 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
effect.Effect(args);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(reaction.Sound, owner);
|
||||
_audio.PlayPvs(reaction.Sound, soln);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,7 +226,7 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
/// Removes the reactants from the solution, then returns a solution with all products.
|
||||
/// WARNING: Does not trigger reactions between solution and new products.
|
||||
/// </summary>
|
||||
private bool ProcessReactions(Solution solution, EntityUid owner, FixedPoint2 maxVolume, SortedSet<ReactionPrototype> reactions, ReactionMixerComponent? mixerComponent)
|
||||
private bool ProcessReactions(Entity<SolutionComponent> soln, SortedSet<ReactionPrototype> reactions, ReactionMixerComponent? mixerComponent)
|
||||
{
|
||||
HashSet<ReactionPrototype> toRemove = new();
|
||||
List<string>? products = null;
|
||||
@@ -230,13 +234,13 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
// attempt to perform any applicable reaction
|
||||
foreach (var reaction in reactions)
|
||||
{
|
||||
if (!CanReact(solution, reaction, owner, mixerComponent, out var unitReactions))
|
||||
if (!CanReact(soln, reaction, mixerComponent, out var unitReactions))
|
||||
{
|
||||
toRemove.Add(reaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
products = PerformReaction(solution, owner, reaction, unitReactions);
|
||||
products = PerformReaction(soln, reaction, unitReactions);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -261,11 +265,11 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
/// <summary>
|
||||
/// Continually react a solution until no more reactions occur, with a volume constraint.
|
||||
/// </summary>
|
||||
public void FullyReactSolution(Solution solution, EntityUid owner, FixedPoint2 maxVolume, ReactionMixerComponent? mixerComponent = null)
|
||||
public void FullyReactSolution(Entity<SolutionComponent> soln, ReactionMixerComponent? mixerComponent = null)
|
||||
{
|
||||
// construct the initial set of reactions to check.
|
||||
SortedSet<ReactionPrototype> reactions = new();
|
||||
foreach (var reactant in solution.Contents)
|
||||
foreach (var reactant in soln.Comp.Solution.Contents)
|
||||
{
|
||||
if (_reactionsSingle.TryGetValue(reactant.Reagent.Prototype, out var reactantReactions))
|
||||
reactions.UnionWith(reactantReactions);
|
||||
@@ -275,11 +279,11 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
// exceed the iteration limit.
|
||||
for (var i = 0; i < MaxReactionIterations; i++)
|
||||
{
|
||||
if (!ProcessReactions(solution, owner, maxVolume, reactions, mixerComponent))
|
||||
if (!ProcessReactions(soln, reactions, mixerComponent))
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Error($"{nameof(Solution)} {owner} could not finish reacting in under {MaxReactionIterations} loops.");
|
||||
Log.Error($"{nameof(Solution)} {soln.Owner} could not finish reacting in under {MaxReactionIterations} loops.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,15 +293,11 @@ namespace Content.Shared.Chemistry.Reaction
|
||||
/// <reamrks>
|
||||
/// Some solution containers (e.g., bloodstream, smoke, foam) use this to block certain reactions from occurring.
|
||||
/// </reamrks>
|
||||
public sealed class ReactionAttemptEvent : CancellableEntityEventArgs
|
||||
[ByRefEvent]
|
||||
public record struct ReactionAttemptEvent(ReactionPrototype Reaction, Entity<SolutionComponent> Solution)
|
||||
{
|
||||
public readonly ReactionPrototype Reaction;
|
||||
public readonly Solution Solution;
|
||||
|
||||
public ReactionAttemptEvent(ReactionPrototype reaction, Solution solution)
|
||||
{
|
||||
Reaction = reaction;
|
||||
Solution = solution;
|
||||
}
|
||||
public readonly ReactionPrototype Reaction = Reaction;
|
||||
public readonly Entity<SolutionComponent> Solution = Solution;
|
||||
public bool Cancelled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,4 @@ public record struct MixingAttemptEvent(EntityUid Mixed, bool Cancelled = false)
|
||||
public readonly record struct AfterMixingEvent(EntityUid Mixed, EntityUid Mixer);
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetMixableSolutionAttemptEvent(EntityUid Mixed, Solution? MixedSolution = null);
|
||||
public record struct GetMixableSolutionAttemptEvent(EntityUid Mixed, Entity<SolutionComponent>? MixedSolution = null);
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -19,6 +20,9 @@ public sealed partial class DrainComponent : Component
|
||||
[ValidatePrototypeId<TagPrototype>]
|
||||
public const string PlungerTag = "Plunger";
|
||||
|
||||
[DataField]
|
||||
public Entity<SolutionComponent>? Solution = null;
|
||||
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -17,5 +18,8 @@ namespace Content.Shared.Fluids.Components
|
||||
public FixedPoint2 OverflowVolume = FixedPoint2.New(20);
|
||||
|
||||
[DataField("solution")] public string SolutionName = "puddle";
|
||||
|
||||
[DataField("solutionRef")]
|
||||
public Entity<SolutionComponent>? Solution;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Fluids.Components;
|
||||
|
||||
namespace Content.Shared.Fluids;
|
||||
|
||||
@@ -22,12 +21,12 @@ public abstract class SharedPuddleSystem : EntitySystem
|
||||
SubscribeLocalEvent<RefillableSolutionComponent, CanDropDraggedEvent>(OnRefillableCanDropDragged);
|
||||
}
|
||||
|
||||
private void OnRefillableCanDrag(EntityUid uid, RefillableSolutionComponent component, ref CanDragEvent args)
|
||||
private void OnRefillableCanDrag(Entity<RefillableSolutionComponent> entity, ref CanDragEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDumpCanDropTarget(EntityUid uid, DumpableSolutionComponent component, ref CanDropTargetEvent args)
|
||||
private void OnDumpCanDropTarget(Entity<DumpableSolutionComponent> entity, ref CanDropTargetEvent args)
|
||||
{
|
||||
if (HasComp<DrainableSolutionComponent>(args.Dragged))
|
||||
{
|
||||
@@ -36,7 +35,7 @@ public abstract class SharedPuddleSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrainCanDropTarget(EntityUid uid, DrainableSolutionComponent component, ref CanDropTargetEvent args)
|
||||
private void OnDrainCanDropTarget(Entity<DrainableSolutionComponent> entity, ref CanDropTargetEvent args)
|
||||
{
|
||||
if (HasComp<RefillableSolutionComponent>(args.Dragged))
|
||||
{
|
||||
@@ -45,7 +44,7 @@ public abstract class SharedPuddleSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRefillableCanDropDragged(EntityUid uid, RefillableSolutionComponent component, ref CanDropDraggedEvent args)
|
||||
private void OnRefillableCanDropDragged(Entity<RefillableSolutionComponent> entity, ref CanDropDraggedEvent args)
|
||||
{
|
||||
if (!HasComp<DrainableSolutionComponent>(args.Target) && !HasComp<DumpableSolutionComponent>(args.Target))
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -84,12 +83,6 @@ public sealed partial class MaterialReclaimerComponent : Component
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string SolutionContainerId = "output";
|
||||
|
||||
/// <summary>
|
||||
/// The solution itself.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Solution OutputSolution = default!;
|
||||
|
||||
/// <summary>
|
||||
/// a whitelist for what entities can be inserted into this reclaimer
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
|
||||
namespace Content.Shared.Nutrition;
|
||||
|
||||
@@ -47,8 +47,8 @@ public sealed partial class VapeDoAfterEvent : DoAfterEvent
|
||||
|
||||
public VapeDoAfterEvent(Solution solution, bool forced)
|
||||
{
|
||||
Solution = solution;
|
||||
Forced = forced;
|
||||
Solution = solution;
|
||||
Forced = forced;
|
||||
}
|
||||
|
||||
public override DoAfterEvent Clone() => this;
|
||||
|
||||
Reference in New Issue
Block a user