| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220 |
- 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.Numerics;
- using System.Runtime.CompilerServices;
- using System.Text;
- using Content.Shared.Hands.Components;
- using Content.Shared.Hands.EntitySystems;
- using Robust.Shared.Map;
- using Robust.Shared.Network;
- 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;
- }
- [ByRefEvent]
- public partial record struct SolutionAccessAttemptEvent(string SolutionName)
- {
- public bool Cancelled;
- }
- /// <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 SharedHandsSystem Hands = default!;
- [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
- [Dependency] protected readonly MetaDataSystem MetaDataSys = default!;
- [Dependency] protected readonly INetManager NetManager = default!;
- public override void Initialize()
- {
- base.Initialize();
- InitializeRelays();
- SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
- SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnSolutionStartup);
- SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnSolutionShutdown);
- SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnContainerManagerInit);
- SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
- SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
- SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnMapInit);
- if (NetManager.IsServer)
- {
- SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentShutdown>(OnContainerManagerShutdown);
- SubscribeLocalEvent<ContainedSolutionComponent, ComponentShutdown>(OnContainedSolutionShutdown);
- }
- }
- /// <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>
- /// /// <param name="errorOnMissing">Should we print an error if the solution specified by name is missing</param>
- /// <returns></returns>
- public bool TryGetSolution(
- Entity<SolutionContainerManagerComponent?> container,
- string? name,
- [NotNullWhen(true)] out Entity<SolutionComponent>? entity,
- [NotNullWhen(true)] out Solution? solution,
- bool errorOnMissing = false)
- {
- if (!TryGetSolution(container, name, out entity, errorOnMissing: errorOnMissing))
- {
- 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,
- bool errorOnMissing = false)
- {
- 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
- )
- {
- var attemptEv = new SolutionAccessAttemptEvent(name);
- RaiseLocalEvent(container, ref attemptEv);
- if (attemptEv.Cancelled)
- {
- entity = null;
- return false;
- }
- uid = containedSolution;
- }
- else
- {
- entity = null;
- if (!errorOnMissing)
- return false;
- Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
- return false;
- }
- if (!TryComp(uid, out SolutionComponent? comp))
- {
- entity = null;
- if (!errorOnMissing)
- return false;
- Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
- return false;
- }
- entity = (uid, comp);
- return true;
- }
- /// <summary>
- /// Version of TryGetSolution that doesn't take or return an entity.
- /// Used for prototypes and with old code parity.
- public bool TryGetSolution(SolutionContainerManagerComponent container,
- string name,
- [NotNullWhen(true)] out Solution? solution,
- bool errorOnMissing = false)
- {
- solution = null;
- if (container.Solutions != null)
- return container.Solutions.TryGetValue(name, out solution);
- if (!errorOnMissing)
- return false;
- Log.Error($"{container} does not have a solution with ID: {name}");
- return false;
- }
- 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)
- {
- var attemptEv = new SolutionAccessAttemptEvent(name);
- RaiseLocalEvent(container, ref attemptEv);
- if (attemptEv.Cancelled)
- continue;
- 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);
- }
- 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);
- }
- /// <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, List<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, List<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, List<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 OnSolutionStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
- {
- UpdateChemicals(entity);
- }
- private void OnSolutionShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
- {
- RemoveAllSolution(entity);
- }
- private void OnContainerManagerInit(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;
- }
- if (!CanSeeHiddenSolution(entity, args.Examiner))
- 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";
- using (args.PushGroup(nameof(ExaminableSolutionComponent)))
- {
- 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;
- }
- if (!CanSeeHiddenSolution(entity, args.User))
- 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.AddMarkupOrThrow(Loc.GetString("scannable-solution-empty-container"));
- return msg;
- }
- msg.AddMarkupOrThrow(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.AddMarkupOrThrow(Loc.GetString("scannable-solution-chemical"
- , ("type", proto.LocalizedName)
- , ("color", proto.SubstanceColor.ToHexNoAlpha())
- , ("amount", quantity)));
- }
- msg.PushNewline();
- msg.AddMarkupOrThrow(Loc.GetString("scannable-solution-temperature", ("temperature", Math.Round(solution.Temperature))));
- return msg;
- }
- /// <summary>
- /// Check if examinable solution requires you to hold the item in hand.
- /// </summary>
- private bool CanSeeHiddenSolution(Entity<ExaminableSolutionComponent> entity, EntityUid examiner)
- {
- // If not held-only then it's always visible.
- if (!entity.Comp.HeldOnly)
- return true;
- if (TryComp(examiner, out HandsComponent? handsComp))
- {
- return Hands.IsHolding(examiner, entity, out _, handsComp);
- }
- return true;
- }
- private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
- {
- EnsureAllSolutions(entity);
- }
- private void OnContainerManagerShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
- {
- foreach (var name in entity.Comp.Containers)
- {
- if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer))
- ContainerSystem.ShutdownContainer(solutionContainer);
- }
- entity.Comp.Containers.Clear();
- }
- private void OnContainedSolutionShutdown(Entity<ContainedSolutionComponent> entity, ref ComponentShutdown args)
- {
- if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container))
- {
- container.Containers.Remove(entity.Comp.ContainerName);
- Dirty(entity.Comp.Container, container);
- }
- if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer))
- ContainerSystem.ShutdownContainer(solutionContainer);
- }
- #endregion Event Handlers
- public bool EnsureSolution(
- Entity<MetaDataComponent?> entity,
- string name,
- [NotNullWhen(true)]out Solution? solution,
- FixedPoint2 maxVol = default)
- {
- return EnsureSolution(entity, name, maxVol, null, out _, out solution);
- }
- public bool EnsureSolution(
- Entity<MetaDataComponent?> entity,
- string name,
- out bool existed,
- [NotNullWhen(true)]out Solution? solution,
- FixedPoint2 maxVol = default)
- {
- return EnsureSolution(entity, name, maxVol, null, out existed, out solution);
- }
- public bool EnsureSolution(
- Entity<MetaDataComponent?> entity,
- string name,
- FixedPoint2 maxVol,
- Solution? prototype,
- out bool existed,
- [NotNullWhen(true)] out Solution? solution)
- {
- solution = null;
- existed = false;
- var (uid, meta) = entity;
- if (!Resolve(uid, ref meta))
- throw new InvalidOperationException("Attempted to ensure solution on invalid entity.");
- var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
- if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
- {
- EnsureSolutionEntity((uid, manager), name, out existed,
- out var solEnt, maxVol, prototype);
- solution = solEnt!.Value.Comp.Solution;
- return true;
- }
- solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
- return true;
- }
- public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
- {
- if (NetManager.IsClient)
- return;
- if (entity.Comp.Solutions is not { } prototypes)
- return;
- foreach (var (name, prototype) in prototypes)
- {
- EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype);
- }
- entity.Comp.Solutions = null;
- Dirty(entity);
- }
- public bool EnsureSolutionEntity(
- Entity<SolutionContainerManagerComponent?> entity,
- string name,
- [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
- FixedPoint2 maxVol = default) =>
- EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol);
- public bool EnsureSolutionEntity(
- Entity<SolutionContainerManagerComponent?> entity,
- string name,
- out bool existed,
- [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
- FixedPoint2 maxVol = default,
- Solution? prototype = null
- )
- {
- existed = true;
- solutionEntity = null;
- var (uid, container) = entity;
- var solutionSlot = ContainerSystem.EnsureContainer<ContainerSlot>(uid, $"solution@{name}", out existed);
- if (!Resolve(uid, ref container, logMissing: false))
- {
- existed = false;
- container = AddComp<SolutionContainerManagerComponent>(uid);
- container.Containers.Add(name);
- if (NetManager.IsClient)
- return false;
- }
- else if (!existed)
- {
- container.Containers.Add(name);
- Dirty(uid, container);
- }
- var needsInit = false;
- SolutionComponent solutionComp;
- if (solutionSlot.ContainedEntity is not { } solutionId)
- {
- if (NetManager.IsClient)
- return false;
- prototype ??= new() { MaxVolume = maxVol };
- prototype.Name = name;
- (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
- existed = false;
- needsInit = true;
- Dirty(uid, container);
- }
- else
- {
- solutionComp = Comp<SolutionComponent>(solutionId);
- DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name);
- DebugTools.Assert(solutionComp.Solution.Name == name);
- var solution = solutionComp.Solution;
- solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
- // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
- // We want the reagents from the prototype to exist even if something else already created the solution.
- if (prototype is { Volume.Value: > 0 })
- solution.AddSolution(prototype, PrototypeManager);
- Dirty(solutionId, solutionComp);
- }
- if (needsInit)
- EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID);
- solutionEntity = (solutionId, solutionComp);
- return true;
- }
- private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
- {
- existed = true;
- var (uid, container) = entity;
- if (!Resolve(uid, ref container, logMissing: false))
- {
- container = AddComp<SolutionContainerManagerComponent>(uid);
- existed = false;
- }
- if (container.Solutions is null)
- container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity);
- if (!container.Solutions.TryGetValue(name, out var solution))
- {
- solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
- container.Solutions.Add(name, solution);
- existed = false;
- }
- else
- solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
- Dirty(uid, container);
- return solution;
- }
- private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
- {
- var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
- var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
- var solution = new SolutionComponent() { Solution = prototype };
- AddComp(uid, solution);
- var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
- AddComp(uid, relation);
- MetaDataSys.SetEntityName(uid, $"solution - {name}");
- ContainerSystem.Insert(uid, container, force: true);
- return (uid, solution, relation);
- }
- public void AdjustDissolvedReagent(
- Entity<SolutionComponent> dissolvedSolution,
- FixedPoint2 volume,
- ReagentId reagent,
- float concentrationChange)
- {
- if (concentrationChange == 0)
- return;
- var dissolvedSol = dissolvedSolution.Comp.Solution;
- var amtChange =
- GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange));
- if (concentrationChange > 0)
- {
- dissolvedSol.AddReagent(reagent, amtChange);
- }
- else
- {
- dissolvedSol.RemoveReagent(reagent,amtChange);
- }
- UpdateChemicals(dissolvedSolution);
- }
- public FixedPoint2 GetReagentQuantityFromConcentration(Entity<SolutionComponent> dissolvedSolution,
- FixedPoint2 volume,float concentration)
- {
- var dissolvedSol = dissolvedSolution.Comp.Solution;
- if (volume == 0
- || dissolvedSol.Volume == 0)
- return 0;
- return concentration * volume;
- }
- public float GetReagentConcentration(Entity<SolutionComponent> dissolvedSolution,
- FixedPoint2 volume, ReagentId dissolvedReagent)
- {
- var dissolvedSol = dissolvedSolution.Comp.Solution;
- if (volume == 0
- || dissolvedSol.Volume == 0
- || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
- return 0;
- return (float)dissolvedVol / volume.Float();
- }
- public FixedPoint2 ClampReagentAmountByConcentration(
- Entity<SolutionComponent> dissolvedSolution,
- FixedPoint2 volume,
- ReagentId dissolvedReagent,
- FixedPoint2 dissolvedReagentAmount,
- float maxConcentration = 1f)
- {
- var dissolvedSol = dissolvedSolution.Comp.Solution;
- if (volume == 0
- || dissolvedSol.Volume == 0
- || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
- return 0;
- volume *= maxConcentration;
- dissolvedVol += dissolvedReagentAmount;
- var overflow = volume - dissolvedVol;
- if (overflow < 0)
- dissolvedReagentAmount += overflow;
- return dissolvedReagentAmount;
- }
- }
|