1
0

SharedSolutionContainerSystem.cs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220
  1. using Content.Shared.Chemistry.Components;
  2. using Content.Shared.Chemistry.Components.SolutionManager;
  3. using Content.Shared.Chemistry.Reaction;
  4. using Content.Shared.Chemistry.Reagent;
  5. using Content.Shared.Examine;
  6. using Content.Shared.FixedPoint;
  7. using Content.Shared.Verbs;
  8. using JetBrains.Annotations;
  9. using Robust.Shared.Containers;
  10. using Robust.Shared.Prototypes;
  11. using Robust.Shared.Utility;
  12. using System.Diagnostics.CodeAnalysis;
  13. using System.Linq;
  14. using System.Numerics;
  15. using System.Runtime.CompilerServices;
  16. using System.Text;
  17. using Content.Shared.Hands.Components;
  18. using Content.Shared.Hands.EntitySystems;
  19. using Robust.Shared.Map;
  20. using Robust.Shared.Network;
  21. using Dependency = Robust.Shared.IoC.DependencyAttribute;
  22. namespace Content.Shared.Chemistry.EntitySystems;
  23. /// <summary>
  24. /// The event raised whenever a solution entity is modified.
  25. /// </summary>
  26. /// <remarks>
  27. /// Raised after chemcial reactions and <see cref="SolutionOverflowEvent"/> are handled.
  28. /// </remarks>
  29. /// <param name="Solution">The solution entity that has been modified.</param>
  30. [ByRefEvent]
  31. public readonly partial record struct SolutionChangedEvent(Entity<SolutionComponent> Solution);
  32. /// <summary>
  33. /// The event raised whenever a solution entity is filled past its capacity.
  34. /// </summary>
  35. /// <param name="Solution">The solution entity that has been overfilled.</param>
  36. /// <param name="Overflow">The amount by which the solution entity has been overfilled.</param>
  37. [ByRefEvent]
  38. public partial record struct SolutionOverflowEvent(Entity<SolutionComponent> Solution, FixedPoint2 Overflow)
  39. {
  40. /// <summary>The solution entity that has been overfilled.</summary>
  41. public readonly Entity<SolutionComponent> Solution = Solution;
  42. /// <summary>The amount by which the solution entity has been overfilled.</summary>
  43. public readonly FixedPoint2 Overflow = Overflow;
  44. /// <summary>Whether any of the event handlers for this event have handled overflow behaviour.</summary>
  45. public bool Handled = false;
  46. }
  47. [ByRefEvent]
  48. public partial record struct SolutionAccessAttemptEvent(string SolutionName)
  49. {
  50. public bool Cancelled;
  51. }
  52. /// <summary>
  53. /// Part of Chemistry system deal with SolutionContainers
  54. /// </summary>
  55. [UsedImplicitly]
  56. public abstract partial class SharedSolutionContainerSystem : EntitySystem
  57. {
  58. [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
  59. [Dependency] protected readonly ChemicalReactionSystem ChemicalReactionSystem = default!;
  60. [Dependency] protected readonly ExamineSystemShared ExamineSystem = default!;
  61. [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
  62. [Dependency] protected readonly SharedHandsSystem Hands = default!;
  63. [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
  64. [Dependency] protected readonly MetaDataSystem MetaDataSys = default!;
  65. [Dependency] protected readonly INetManager NetManager = default!;
  66. public override void Initialize()
  67. {
  68. base.Initialize();
  69. InitializeRelays();
  70. SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
  71. SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnSolutionStartup);
  72. SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnSolutionShutdown);
  73. SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnContainerManagerInit);
  74. SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
  75. SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
  76. SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnMapInit);
  77. if (NetManager.IsServer)
  78. {
  79. SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentShutdown>(OnContainerManagerShutdown);
  80. SubscribeLocalEvent<ContainedSolutionComponent, ComponentShutdown>(OnContainedSolutionShutdown);
  81. }
  82. }
  83. /// <summary>
  84. /// Attempts to resolve a solution associated with an entity.
  85. /// </summary>
  86. /// <param name="container">The entity that holdes the container the solution entity is in.</param>
  87. /// <param name="name">The name of the solution entities container.</param>
  88. /// <param name="entity">A reference to a solution entity to load the associated solution entity into. Will be unchanged if not null.</param>
  89. /// <param name="solution">Returns the solution state of the solution entity.</param>
  90. /// <returns>Whether the solution was successfully resolved.</returns>
  91. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  92. public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
  93. {
  94. if (!ResolveSolution(container, name, ref entity))
  95. {
  96. solution = null;
  97. return false;
  98. }
  99. solution = entity.Value.Comp.Solution;
  100. return true;
  101. }
  102. /// <inheritdoc cref="ResolveSolution"/>
  103. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  104. public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity)
  105. {
  106. if (entity is not null)
  107. {
  108. DebugTools.Assert(TryGetSolution(container, name, out var debugEnt)
  109. && debugEnt.Value.Owner == entity.Value.Owner);
  110. return true;
  111. }
  112. return TryGetSolution(container, name, out entity);
  113. }
  114. /// <summary>
  115. /// Attempts to fetch a solution entity associated with an entity.
  116. /// </summary>
  117. /// <remarks>
  118. /// If the solution entity will be frequently accessed please use the equivalent <see cref="ResolveSolution"/> method and cache the result.
  119. /// </remarks>
  120. /// <param name="container">The entity the solution entity should be associated with.</param>
  121. /// <param name="name">The name of the solution entity to fetch.</param>
  122. /// <param name="entity">Returns the solution entity that was fetched.</param>
  123. /// <param name="solution">Returns the solution state of the solution entity that was fetched.</param>
  124. /// /// <param name="errorOnMissing">Should we print an error if the solution specified by name is missing</param>
  125. /// <returns></returns>
  126. public bool TryGetSolution(
  127. Entity<SolutionContainerManagerComponent?> container,
  128. string? name,
  129. [NotNullWhen(true)] out Entity<SolutionComponent>? entity,
  130. [NotNullWhen(true)] out Solution? solution,
  131. bool errorOnMissing = false)
  132. {
  133. if (!TryGetSolution(container, name, out entity, errorOnMissing: errorOnMissing))
  134. {
  135. solution = null;
  136. return false;
  137. }
  138. solution = entity.Value.Comp.Solution;
  139. return true;
  140. }
  141. /// <inheritdoc cref="TryGetSolution"/>
  142. public bool TryGetSolution(
  143. Entity<SolutionContainerManagerComponent?> container,
  144. string? name,
  145. [NotNullWhen(true)] out Entity<SolutionComponent>? entity,
  146. bool errorOnMissing = false)
  147. {
  148. EntityUid uid;
  149. if (name is null)
  150. uid = container;
  151. else if (
  152. ContainerSystem.TryGetContainer(container, $"solution@{name}", out var solutionContainer) &&
  153. solutionContainer is ContainerSlot solutionSlot &&
  154. solutionSlot.ContainedEntity is { } containedSolution
  155. )
  156. {
  157. var attemptEv = new SolutionAccessAttemptEvent(name);
  158. RaiseLocalEvent(container, ref attemptEv);
  159. if (attemptEv.Cancelled)
  160. {
  161. entity = null;
  162. return false;
  163. }
  164. uid = containedSolution;
  165. }
  166. else
  167. {
  168. entity = null;
  169. if (!errorOnMissing)
  170. return false;
  171. Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
  172. return false;
  173. }
  174. if (!TryComp(uid, out SolutionComponent? comp))
  175. {
  176. entity = null;
  177. if (!errorOnMissing)
  178. return false;
  179. Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
  180. return false;
  181. }
  182. entity = (uid, comp);
  183. return true;
  184. }
  185. /// <summary>
  186. /// Version of TryGetSolution that doesn't take or return an entity.
  187. /// Used for prototypes and with old code parity.
  188. public bool TryGetSolution(SolutionContainerManagerComponent container,
  189. string name,
  190. [NotNullWhen(true)] out Solution? solution,
  191. bool errorOnMissing = false)
  192. {
  193. solution = null;
  194. if (container.Solutions != null)
  195. return container.Solutions.TryGetValue(name, out solution);
  196. if (!errorOnMissing)
  197. return false;
  198. Log.Error($"{container} does not have a solution with ID: {name}");
  199. return false;
  200. }
  201. public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionContainerManagerComponent?> container, bool includeSelf = true)
  202. {
  203. if (includeSelf && TryComp(container, out SolutionComponent? solutionComp))
  204. yield return (null, (container.Owner, solutionComp));
  205. if (!Resolve(container, ref container.Comp, logMissing: false))
  206. yield break;
  207. foreach (var name in container.Comp.Containers)
  208. {
  209. var attemptEv = new SolutionAccessAttemptEvent(name);
  210. RaiseLocalEvent(container, ref attemptEv);
  211. if (attemptEv.Cancelled)
  212. continue;
  213. if (ContainerSystem.GetContainer(container, $"solution@{name}") is ContainerSlot slot && slot.ContainedEntity is { } solutionId)
  214. yield return (name, (solutionId, Comp<SolutionComponent>(solutionId)));
  215. }
  216. }
  217. public IEnumerable<(string Name, Solution Solution)> EnumerateSolutions(SolutionContainerManagerComponent container)
  218. {
  219. if (container.Solutions is not { Count: > 0 } solutions)
  220. yield break;
  221. foreach (var (name, solution) in solutions)
  222. {
  223. yield return (name, solution);
  224. }
  225. }
  226. protected void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent, ContainedSolutionComponent> soln)
  227. {
  228. var (uid, appearanceComponent) = container;
  229. if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false))
  230. return;
  231. var (_, comp, relation) = soln;
  232. var solution = comp.Solution;
  233. AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
  234. AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
  235. AppearanceSystem.SetData(uid, SolutionContainerVisuals.SolutionName, relation.ContainerName, appearanceComponent);
  236. if (solution.GetPrimaryReagentId() is { } reagent)
  237. AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
  238. }
  239. public FixedPoint2 GetTotalPrototypeQuantity(EntityUid owner, string reagentId)
  240. {
  241. var reagentQuantity = FixedPoint2.New(0);
  242. if (EntityManager.EntityExists(owner)
  243. && EntityManager.TryGetComponent(owner, out SolutionContainerManagerComponent? managerComponent))
  244. {
  245. foreach (var (_, soln) in EnumerateSolutions((owner, managerComponent)))
  246. {
  247. var solution = soln.Comp.Solution;
  248. reagentQuantity += solution.GetTotalPrototypeQuantity(reagentId);
  249. }
  250. }
  251. return reagentQuantity;
  252. }
  253. /// <summary>
  254. /// Dirties a solution entity that has been modified and prompts updates to chemical reactions and overflow state.
  255. /// Should be invoked whenever a solution entity is modified.
  256. /// </summary>
  257. /// <remarks>
  258. /// 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.
  259. /// </remarks>
  260. /// <param name="soln"></param>
  261. /// <param name="needsReactionsProcessing"></param>
  262. /// <param name="mixerComponent"></param>
  263. public void UpdateChemicals(Entity<SolutionComponent> soln, bool needsReactionsProcessing = true, ReactionMixerComponent? mixerComponent = null)
  264. {
  265. Dirty(soln);
  266. var (uid, comp) = soln;
  267. var solution = comp.Solution;
  268. // Process reactions
  269. if (needsReactionsProcessing && solution.CanReact)
  270. ChemicalReactionSystem.FullyReactSolution(soln, mixerComponent);
  271. var overflow = solution.Volume - solution.MaxVolume;
  272. if (overflow > FixedPoint2.Zero)
  273. {
  274. var overflowEv = new SolutionOverflowEvent(soln, overflow);
  275. RaiseLocalEvent(uid, ref overflowEv);
  276. }
  277. UpdateAppearance((uid, comp, null));
  278. var changedEv = new SolutionChangedEvent(soln);
  279. RaiseLocalEvent(uid, ref changedEv);
  280. }
  281. public void UpdateAppearance(Entity<SolutionComponent, AppearanceComponent?> soln)
  282. {
  283. var (uid, comp, appearanceComponent) = soln;
  284. var solution = comp.Solution;
  285. if (!EntityManager.EntityExists(uid) || !Resolve(uid, ref appearanceComponent, false))
  286. return;
  287. AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
  288. AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
  289. if (solution.GetPrimaryReagentId() is { } reagent)
  290. AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
  291. }
  292. /// <summary>
  293. /// Removes part of the solution in the container.
  294. /// </summary>
  295. /// <param name="targetUid"></param>
  296. /// <param name="solutionHolder"></param>
  297. /// <param name="quantity">the volume of solution to remove.</param>
  298. /// <returns>The solution that was removed.</returns>
  299. public Solution SplitSolution(Entity<SolutionComponent> soln, FixedPoint2 quantity)
  300. {
  301. var (uid, comp) = soln;
  302. var solution = comp.Solution;
  303. var splitSol = solution.SplitSolution(quantity);
  304. UpdateChemicals(soln);
  305. return splitSol;
  306. }
  307. public Solution SplitStackSolution(Entity<SolutionComponent> soln, FixedPoint2 quantity, int stackCount)
  308. {
  309. var (uid, comp) = soln;
  310. var solution = comp.Solution;
  311. var splitSol = solution.SplitSolution(quantity / stackCount);
  312. solution.SplitSolution(quantity - splitSol.Volume);
  313. UpdateChemicals(soln);
  314. return splitSol;
  315. }
  316. /// <summary>
  317. /// Splits a solution without the specified reagent(s).
  318. /// </summary>
  319. public Solution SplitSolutionWithout(Entity<SolutionComponent> soln, FixedPoint2 quantity, params string[] reagents)
  320. {
  321. var (uid, comp) = soln;
  322. var solution = comp.Solution;
  323. var splitSol = solution.SplitSolutionWithout(quantity, reagents);
  324. UpdateChemicals(soln);
  325. return splitSol;
  326. }
  327. public void RemoveAllSolution(Entity<SolutionComponent> soln)
  328. {
  329. var (uid, comp) = soln;
  330. var solution = comp.Solution;
  331. if (solution.Volume == 0)
  332. return;
  333. solution.RemoveAllSolution();
  334. UpdateChemicals(soln);
  335. }
  336. /// <summary>
  337. /// Sets the capacity (maximum volume) of a solution to a new value.
  338. /// </summary>
  339. /// <param name="targetUid">The entity containing the solution.</param>
  340. /// <param name="targetSolution">The solution to set the capacity of.</param>
  341. /// <param name="capacity">The value to set the capacity of the solution to.</param>
  342. public void SetCapacity(Entity<SolutionComponent> soln, FixedPoint2 capacity)
  343. {
  344. var (uid, comp) = soln;
  345. var solution = comp.Solution;
  346. if (solution.MaxVolume == capacity)
  347. return;
  348. solution.MaxVolume = capacity;
  349. UpdateChemicals(soln);
  350. }
  351. /// <summary>
  352. /// Adds reagent of an Id to the container.
  353. /// </summary>
  354. /// <param name="targetUid"></param>
  355. /// <param name="targetSolution">Container to which we are adding reagent</param>
  356. /// <param name="reagentQuantity">The reagent to add.</param>
  357. /// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
  358. /// <returns>If all the reagent could be added.</returns>
  359. public bool TryAddReagent(Entity<SolutionComponent> soln, ReagentQuantity reagentQuantity, out FixedPoint2 acceptedQuantity, float? temperature = null)
  360. {
  361. var (uid, comp) = soln;
  362. var solution = comp.Solution;
  363. acceptedQuantity = solution.AvailableVolume > reagentQuantity.Quantity
  364. ? reagentQuantity.Quantity
  365. : solution.AvailableVolume;
  366. if (acceptedQuantity <= 0)
  367. return reagentQuantity.Quantity == 0;
  368. if (temperature == null)
  369. {
  370. solution.AddReagent(reagentQuantity.Reagent, acceptedQuantity);
  371. }
  372. else
  373. {
  374. var proto = PrototypeManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
  375. solution.AddReagent(proto, acceptedQuantity, temperature.Value, PrototypeManager);
  376. }
  377. UpdateChemicals(soln);
  378. return acceptedQuantity == reagentQuantity.Quantity;
  379. }
  380. /// <summary>
  381. /// Adds reagent of an Id to the container.
  382. /// </summary>
  383. /// <param name="targetUid"></param>
  384. /// <param name="targetSolution">Container to which we are adding reagent</param>
  385. /// <param name="prototype">The Id of the reagent to add.</param>
  386. /// <param name="quantity">The amount of reagent to add.</param>
  387. /// <returns>If all the reagent could be added.</returns>
  388. [PublicAPI]
  389. public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, float? temperature = null, List<ReagentData>? data = null)
  390. => TryAddReagent(soln, new ReagentQuantity(prototype, quantity, data), out _, temperature);
  391. /// <summary>
  392. /// Adds reagent of an Id to the container.
  393. /// </summary>
  394. /// <param name="targetUid"></param>
  395. /// <param name="targetSolution">Container to which we are adding reagent</param>
  396. /// <param name="prototype">The Id of the reagent to add.</param>
  397. /// <param name="quantity">The amount of reagent to add.</param>
  398. /// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
  399. /// <returns>If all the reagent could be added.</returns>
  400. public bool TryAddReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null, List<ReagentData>? data = null)
  401. {
  402. var reagent = new ReagentQuantity(prototype, quantity, data);
  403. return TryAddReagent(soln, reagent, out acceptedQuantity, temperature);
  404. }
  405. /// <summary>
  406. /// Adds reagent of an Id to the container.
  407. /// </summary>
  408. /// <param name="targetUid"></param>
  409. /// <param name="targetSolution">Container to which we are adding reagent</param>
  410. /// <param name="reagentId">The reagent to add.</param>
  411. /// <param name="quantity">The amount of reagent to add.</param>
  412. /// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
  413. /// <returns>If all the reagent could be added.</returns>
  414. public bool TryAddReagent(Entity<SolutionComponent> soln, ReagentId reagentId, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null)
  415. {
  416. var quant = new ReagentQuantity(reagentId, quantity);
  417. return TryAddReagent(soln, quant, out acceptedQuantity, temperature);
  418. }
  419. /// <summary>
  420. /// Removes reagent from a container.
  421. /// </summary>
  422. /// <param name="targetUid"></param>
  423. /// <param name="container">Solution container from which we are removing reagent</param>
  424. /// <param name="reagentQuantity">The reagent to remove.</param>
  425. /// <returns>If the reagent to remove was found in the container.</returns>
  426. public bool RemoveReagent(Entity<SolutionComponent> soln, ReagentQuantity reagentQuantity)
  427. {
  428. var (uid, comp) = soln;
  429. var solution = comp.Solution;
  430. var quant = solution.RemoveReagent(reagentQuantity);
  431. if (quant <= FixedPoint2.Zero)
  432. return false;
  433. UpdateChemicals(soln);
  434. return true;
  435. }
  436. /// <summary>
  437. /// Removes reagent from a container.
  438. /// </summary>
  439. /// <param name="targetUid"></param>
  440. /// <param name="container">Solution container from which we are removing reagent</param>
  441. /// <param name="prototype">The Id of the reagent to remove.</param>
  442. /// <param name="quantity">The amount of reagent to remove.</param>
  443. /// <returns>If the reagent to remove was found in the container.</returns>
  444. public bool RemoveReagent(Entity<SolutionComponent> soln, string prototype, FixedPoint2 quantity, List<ReagentData>? data = null)
  445. {
  446. return RemoveReagent(soln, new ReagentQuantity(prototype, quantity, data));
  447. }
  448. /// <summary>
  449. /// Removes reagent from a container.
  450. /// </summary>
  451. /// <param name="targetUid"></param>
  452. /// <param name="container">Solution container from which we are removing reagent</param>
  453. /// <param name="reagentId">The reagent to remove.</param>
  454. /// <param name="quantity">The amount of reagent to remove.</param>
  455. /// <returns>If the reagent to remove was found in the container.</returns>
  456. public bool RemoveReagent(Entity<SolutionComponent> soln, ReagentId reagentId, FixedPoint2 quantity)
  457. {
  458. return RemoveReagent(soln, new ReagentQuantity(reagentId, quantity));
  459. }
  460. /// <summary>
  461. /// Moves some quantity of a solution from one solution to another.
  462. /// </summary>
  463. /// <param name="sourceUid">entity holding the source solution</param>
  464. /// <param name="targetUid">entity holding the target solution</param>
  465. /// <param name="source">source solution</param>
  466. /// <param name="target">target solution</param>
  467. /// <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>
  468. public bool TryTransferSolution(Entity<SolutionComponent> soln, Solution source, FixedPoint2 quantity)
  469. {
  470. var (uid, comp) = soln;
  471. var solution = comp.Solution;
  472. if (quantity < 0)
  473. throw new InvalidOperationException("Quantity must be positive");
  474. quantity = FixedPoint2.Min(quantity, solution.AvailableVolume, source.Volume);
  475. if (quantity == 0)
  476. return false;
  477. // TODO This should be made into a function that directly transfers reagents.
  478. // Currently this is quite inefficient.
  479. solution.AddSolution(source.SplitSolution(quantity), PrototypeManager);
  480. UpdateChemicals(soln);
  481. return true;
  482. }
  483. /// <summary>
  484. /// Adds a solution to the container, if it can fully fit.
  485. /// </summary>
  486. /// <param name="targetUid">entity holding targetSolution</param>
  487. /// <param name="targetSolution">entity holding targetSolution</param>
  488. /// <param name="toAdd">solution being added</param>
  489. /// <returns>If the solution could be added.</returns>
  490. public bool TryAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
  491. {
  492. var (uid, comp) = soln;
  493. var solution = comp.Solution;
  494. if (toAdd.Volume == FixedPoint2.Zero)
  495. return true;
  496. if (toAdd.Volume > solution.AvailableVolume)
  497. return false;
  498. ForceAddSolution(soln, toAdd);
  499. return true;
  500. }
  501. /// <summary>
  502. /// Adds as much of a solution to a container as can fit.
  503. /// </summary>
  504. /// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
  505. /// <param name="targetSolution">The solution being added to.</param>
  506. /// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
  507. /// <returns>The quantity of the solution actually added.</returns>
  508. public FixedPoint2 AddSolution(Entity<SolutionComponent> soln, Solution toAdd)
  509. {
  510. var (uid, comp) = soln;
  511. var solution = comp.Solution;
  512. if (toAdd.Volume == FixedPoint2.Zero)
  513. return FixedPoint2.Zero;
  514. var quantity = FixedPoint2.Max(FixedPoint2.Zero, FixedPoint2.Min(toAdd.Volume, solution.AvailableVolume));
  515. if (quantity < toAdd.Volume)
  516. TryTransferSolution(soln, toAdd, quantity);
  517. else
  518. ForceAddSolution(soln, toAdd);
  519. return quantity;
  520. }
  521. /// <summary>
  522. /// Adds a solution to a container and updates the container.
  523. /// </summary>
  524. /// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
  525. /// <param name="targetSolution">The solution being added to.</param>
  526. /// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
  527. /// <returns>Whether any reagents were added to the solution.</returns>
  528. public bool ForceAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
  529. {
  530. var (uid, comp) = soln;
  531. var solution = comp.Solution;
  532. if (toAdd.Volume == FixedPoint2.Zero)
  533. return false;
  534. solution.AddSolution(toAdd, PrototypeManager);
  535. UpdateChemicals(soln);
  536. return true;
  537. }
  538. /// <summary>
  539. /// Adds a solution to the container, removing the overflow.
  540. /// Unlike <see cref="TryAddSolution"/> it will ignore size limits.
  541. /// </summary>
  542. /// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
  543. /// <param name="targetSolution">The solution being added to.</param>
  544. /// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param>
  545. /// <param name="overflowThreshold">The combined volume above which the overflow will be returned.
  546. /// If the combined volume is below this an empty solution is returned.</param>
  547. /// <param name="overflowingSolution">Solution that exceeded overflowThreshold</param>
  548. /// <returns>Whether any reagents were added to <paramref cref="targetSolution"/>.</returns>
  549. public bool TryMixAndOverflow(Entity<SolutionComponent> soln, Solution toAdd, FixedPoint2 overflowThreshold, [MaybeNullWhen(false)] out Solution overflowingSolution)
  550. {
  551. var (uid, comp) = soln;
  552. var solution = comp.Solution;
  553. if (toAdd.Volume == 0 || overflowThreshold > solution.MaxVolume)
  554. {
  555. overflowingSolution = null;
  556. return false;
  557. }
  558. solution.AddSolution(toAdd, PrototypeManager);
  559. overflowingSolution = solution.SplitSolution(FixedPoint2.Max(FixedPoint2.Zero, solution.Volume - overflowThreshold));
  560. UpdateChemicals(soln);
  561. return true;
  562. }
  563. /// <summary>
  564. /// Removes an amount from all reagents in a solution, adding it to a new solution.
  565. /// </summary>
  566. /// <param name="uid">The entity containing the solution.</param>
  567. /// <param name="solution">The solution to remove reagents from.</param>
  568. /// <param name="quantity">The amount to remove from every reagent in the solution.</param>
  569. /// <returns>A new solution containing every removed reagent from the original solution.</returns>
  570. public Solution RemoveEachReagent(Entity<SolutionComponent> soln, FixedPoint2 quantity)
  571. {
  572. var (uid, comp) = soln;
  573. var solution = comp.Solution;
  574. if (quantity <= 0)
  575. return new Solution();
  576. var removedSolution = new Solution();
  577. // RemoveReagent does a RemoveSwap, meaning we don't have to copy the list if we iterate it backwards.
  578. for (var i = solution.Contents.Count - 1; i >= 0; i--)
  579. {
  580. var (reagent, _) = solution.Contents[i];
  581. var removedQuantity = solution.RemoveReagent(reagent, quantity);
  582. removedSolution.AddReagent(reagent, removedQuantity);
  583. }
  584. UpdateChemicals(soln);
  585. return removedSolution;
  586. }
  587. // Thermal energy and temperature management.
  588. #region Thermal Energy and Temperature
  589. /// <summary>
  590. /// Sets the temperature of a solution to a new value and then checks for reaction processing.
  591. /// </summary>
  592. /// <param name="owner">The entity in which the solution is located.</param>
  593. /// <param name="solution">The solution to set the temperature of.</param>
  594. /// <param name="temperature">The new value to set the temperature to.</param>
  595. public void SetTemperature(Entity<SolutionComponent> soln, float temperature)
  596. {
  597. var (_, comp) = soln;
  598. var solution = comp.Solution;
  599. if (temperature == solution.Temperature)
  600. return;
  601. solution.Temperature = temperature;
  602. UpdateChemicals(soln);
  603. }
  604. /// <summary>
  605. /// Sets the thermal energy of a solution to a new value and then checks for reaction processing.
  606. /// </summary>
  607. /// <param name="owner">The entity in which the solution is located.</param>
  608. /// <param name="solution">The solution to set the thermal energy of.</param>
  609. /// <param name="thermalEnergy">The new value to set the thermal energy to.</param>
  610. public void SetThermalEnergy(Entity<SolutionComponent> soln, float thermalEnergy)
  611. {
  612. var (_, comp) = soln;
  613. var solution = comp.Solution;
  614. var heatCap = solution.GetHeatCapacity(PrototypeManager);
  615. solution.Temperature = heatCap == 0 ? 0 : thermalEnergy / heatCap;
  616. UpdateChemicals(soln);
  617. }
  618. /// <summary>
  619. /// Adds some thermal energy to a solution and then checks for reaction processing.
  620. /// </summary>
  621. /// <param name="owner">The entity in which the solution is located.</param>
  622. /// <param name="solution">The solution to set the thermal energy of.</param>
  623. /// <param name="thermalEnergy">The new value to set the thermal energy to.</param>
  624. public void AddThermalEnergy(Entity<SolutionComponent> soln, float thermalEnergy)
  625. {
  626. var (_, comp) = soln;
  627. var solution = comp.Solution;
  628. if (thermalEnergy == 0.0f)
  629. return;
  630. var heatCap = solution.GetHeatCapacity(PrototypeManager);
  631. solution.Temperature += heatCap == 0 ? 0 : thermalEnergy / heatCap;
  632. UpdateChemicals(soln);
  633. }
  634. #endregion Thermal Energy and Temperature
  635. #region Event Handlers
  636. private void OnComponentInit(Entity<SolutionComponent> entity, ref ComponentInit args)
  637. {
  638. entity.Comp.Solution.ValidateSolution();
  639. }
  640. private void OnSolutionStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
  641. {
  642. UpdateChemicals(entity);
  643. }
  644. private void OnSolutionShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
  645. {
  646. RemoveAllSolution(entity);
  647. }
  648. private void OnContainerManagerInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
  649. {
  650. if (entity.Comp.Containers is not { Count: > 0 } containers)
  651. return;
  652. var containerManager = EnsureComp<ContainerManagerComponent>(entity);
  653. foreach (var name in containers)
  654. {
  655. // The actual solution entity should be directly held within the corresponding slot.
  656. ContainerSystem.EnsureContainer<ContainerSlot>(entity.Owner, $"solution@{name}", containerManager);
  657. }
  658. }
  659. private void OnExamineSolution(Entity<ExaminableSolutionComponent> entity, ref ExaminedEvent args)
  660. {
  661. if (!TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
  662. {
  663. return;
  664. }
  665. if (!CanSeeHiddenSolution(entity, args.Examiner))
  666. return;
  667. var primaryReagent = solution.GetPrimaryReagentId();
  668. if (string.IsNullOrEmpty(primaryReagent?.Prototype))
  669. {
  670. args.PushText(Loc.GetString("shared-solution-container-component-on-examine-empty-container"));
  671. return;
  672. }
  673. if (!PrototypeManager.TryIndex(primaryReagent.Value.Prototype, out ReagentPrototype? primary))
  674. {
  675. Log.Error($"{nameof(Solution)} could not find the prototype associated with {primaryReagent}.");
  676. return;
  677. }
  678. var colorHex = solution.GetColor(PrototypeManager)
  679. .ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
  680. var messageString = "shared-solution-container-component-on-examine-main-text";
  681. using (args.PushGroup(nameof(ExaminableSolutionComponent)))
  682. {
  683. args.PushMarkup(Loc.GetString(messageString,
  684. ("color", colorHex),
  685. ("wordedAmount", Loc.GetString(solution.Contents.Count == 1
  686. ? "shared-solution-container-component-on-examine-worded-amount-one-reagent"
  687. : "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")),
  688. ("desc", primary.LocalizedPhysicalDescription)));
  689. var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager);
  690. // Sort the reagents by amount, descending then alphabetically
  691. var sortedReagentPrototypes = reagentPrototypes
  692. .OrderByDescending(pair => pair.Value.Value)
  693. .ThenBy(pair => pair.Key.LocalizedName);
  694. // Add descriptions of immediately recognizable reagents, like water or beer
  695. var recognized = new List<ReagentPrototype>();
  696. foreach (var keyValuePair in sortedReagentPrototypes)
  697. {
  698. var proto = keyValuePair.Key;
  699. if (!proto.Recognizable)
  700. {
  701. continue;
  702. }
  703. recognized.Add(proto);
  704. }
  705. // Skip if there's nothing recognizable
  706. if (recognized.Count == 0)
  707. return;
  708. var msg = new StringBuilder();
  709. foreach (var reagent in recognized)
  710. {
  711. string part;
  712. if (reagent == recognized[0])
  713. {
  714. part = "examinable-solution-recognized-first";
  715. }
  716. else if (reagent == recognized[^1])
  717. {
  718. // this loc specifically requires space to be appended, fluent doesnt support whitespace
  719. msg.Append(' ');
  720. part = "examinable-solution-recognized-last";
  721. }
  722. else
  723. {
  724. part = "examinable-solution-recognized-next";
  725. }
  726. msg.Append(Loc.GetString(part, ("color", reagent.SubstanceColor.ToHexNoAlpha()),
  727. ("chemical", reagent.LocalizedName)));
  728. }
  729. args.PushMarkup(Loc.GetString("examinable-solution-has-recognizable-chemicals",
  730. ("recognizedString", msg.ToString())));
  731. }
  732. }
  733. private void OnSolutionExaminableVerb(Entity<ExaminableSolutionComponent> entity, ref GetVerbsEvent<ExamineVerb> args)
  734. {
  735. if (!args.CanInteract || !args.CanAccess)
  736. return;
  737. var scanEvent = new SolutionScanEvent();
  738. RaiseLocalEvent(args.User, scanEvent);
  739. if (!scanEvent.CanScan)
  740. {
  741. return;
  742. }
  743. if (!TryGetSolution(args.Target, entity.Comp.Solution, out _, out var solutionHolder))
  744. {
  745. return;
  746. }
  747. if (!CanSeeHiddenSolution(entity, args.User))
  748. return;
  749. var target = args.Target;
  750. var user = args.User;
  751. var verb = new ExamineVerb()
  752. {
  753. Act = () =>
  754. {
  755. var markup = GetSolutionExamine(solutionHolder);
  756. ExamineSystem.SendExamineTooltip(user, target, markup, false, false);
  757. },
  758. Text = Loc.GetString("scannable-solution-verb-text"),
  759. Message = Loc.GetString("scannable-solution-verb-message"),
  760. Category = VerbCategory.Examine,
  761. Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
  762. };
  763. args.Verbs.Add(verb);
  764. }
  765. private FormattedMessage GetSolutionExamine(Solution solution)
  766. {
  767. var msg = new FormattedMessage();
  768. if (solution.Volume == 0)
  769. {
  770. msg.AddMarkupOrThrow(Loc.GetString("scannable-solution-empty-container"));
  771. return msg;
  772. }
  773. msg.AddMarkupOrThrow(Loc.GetString("scannable-solution-main-text"));
  774. var reagentPrototypes = solution.GetReagentPrototypes(PrototypeManager);
  775. // Sort the reagents by amount, descending then alphabetically
  776. var sortedReagentPrototypes = reagentPrototypes
  777. .OrderByDescending(pair => pair.Value.Value)
  778. .ThenBy(pair => pair.Key.LocalizedName);
  779. foreach (var (proto, quantity) in sortedReagentPrototypes)
  780. {
  781. msg.PushNewline();
  782. msg.AddMarkupOrThrow(Loc.GetString("scannable-solution-chemical"
  783. , ("type", proto.LocalizedName)
  784. , ("color", proto.SubstanceColor.ToHexNoAlpha())
  785. , ("amount", quantity)));
  786. }
  787. msg.PushNewline();
  788. msg.AddMarkupOrThrow(Loc.GetString("scannable-solution-temperature", ("temperature", Math.Round(solution.Temperature))));
  789. return msg;
  790. }
  791. /// <summary>
  792. /// Check if examinable solution requires you to hold the item in hand.
  793. /// </summary>
  794. private bool CanSeeHiddenSolution(Entity<ExaminableSolutionComponent> entity, EntityUid examiner)
  795. {
  796. // If not held-only then it's always visible.
  797. if (!entity.Comp.HeldOnly)
  798. return true;
  799. if (TryComp(examiner, out HandsComponent? handsComp))
  800. {
  801. return Hands.IsHolding(examiner, entity, out _, handsComp);
  802. }
  803. return true;
  804. }
  805. private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
  806. {
  807. EnsureAllSolutions(entity);
  808. }
  809. private void OnContainerManagerShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
  810. {
  811. foreach (var name in entity.Comp.Containers)
  812. {
  813. if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer))
  814. ContainerSystem.ShutdownContainer(solutionContainer);
  815. }
  816. entity.Comp.Containers.Clear();
  817. }
  818. private void OnContainedSolutionShutdown(Entity<ContainedSolutionComponent> entity, ref ComponentShutdown args)
  819. {
  820. if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container))
  821. {
  822. container.Containers.Remove(entity.Comp.ContainerName);
  823. Dirty(entity.Comp.Container, container);
  824. }
  825. if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer))
  826. ContainerSystem.ShutdownContainer(solutionContainer);
  827. }
  828. #endregion Event Handlers
  829. public bool EnsureSolution(
  830. Entity<MetaDataComponent?> entity,
  831. string name,
  832. [NotNullWhen(true)]out Solution? solution,
  833. FixedPoint2 maxVol = default)
  834. {
  835. return EnsureSolution(entity, name, maxVol, null, out _, out solution);
  836. }
  837. public bool EnsureSolution(
  838. Entity<MetaDataComponent?> entity,
  839. string name,
  840. out bool existed,
  841. [NotNullWhen(true)]out Solution? solution,
  842. FixedPoint2 maxVol = default)
  843. {
  844. return EnsureSolution(entity, name, maxVol, null, out existed, out solution);
  845. }
  846. public bool EnsureSolution(
  847. Entity<MetaDataComponent?> entity,
  848. string name,
  849. FixedPoint2 maxVol,
  850. Solution? prototype,
  851. out bool existed,
  852. [NotNullWhen(true)] out Solution? solution)
  853. {
  854. solution = null;
  855. existed = false;
  856. var (uid, meta) = entity;
  857. if (!Resolve(uid, ref meta))
  858. throw new InvalidOperationException("Attempted to ensure solution on invalid entity.");
  859. var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
  860. if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
  861. {
  862. EnsureSolutionEntity((uid, manager), name, out existed,
  863. out var solEnt, maxVol, prototype);
  864. solution = solEnt!.Value.Comp.Solution;
  865. return true;
  866. }
  867. solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
  868. return true;
  869. }
  870. public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
  871. {
  872. if (NetManager.IsClient)
  873. return;
  874. if (entity.Comp.Solutions is not { } prototypes)
  875. return;
  876. foreach (var (name, prototype) in prototypes)
  877. {
  878. EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype);
  879. }
  880. entity.Comp.Solutions = null;
  881. Dirty(entity);
  882. }
  883. public bool EnsureSolutionEntity(
  884. Entity<SolutionContainerManagerComponent?> entity,
  885. string name,
  886. [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
  887. FixedPoint2 maxVol = default) =>
  888. EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol);
  889. public bool EnsureSolutionEntity(
  890. Entity<SolutionContainerManagerComponent?> entity,
  891. string name,
  892. out bool existed,
  893. [NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
  894. FixedPoint2 maxVol = default,
  895. Solution? prototype = null
  896. )
  897. {
  898. existed = true;
  899. solutionEntity = null;
  900. var (uid, container) = entity;
  901. var solutionSlot = ContainerSystem.EnsureContainer<ContainerSlot>(uid, $"solution@{name}", out existed);
  902. if (!Resolve(uid, ref container, logMissing: false))
  903. {
  904. existed = false;
  905. container = AddComp<SolutionContainerManagerComponent>(uid);
  906. container.Containers.Add(name);
  907. if (NetManager.IsClient)
  908. return false;
  909. }
  910. else if (!existed)
  911. {
  912. container.Containers.Add(name);
  913. Dirty(uid, container);
  914. }
  915. var needsInit = false;
  916. SolutionComponent solutionComp;
  917. if (solutionSlot.ContainedEntity is not { } solutionId)
  918. {
  919. if (NetManager.IsClient)
  920. return false;
  921. prototype ??= new() { MaxVolume = maxVol };
  922. prototype.Name = name;
  923. (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
  924. existed = false;
  925. needsInit = true;
  926. Dirty(uid, container);
  927. }
  928. else
  929. {
  930. solutionComp = Comp<SolutionComponent>(solutionId);
  931. DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name);
  932. DebugTools.Assert(solutionComp.Solution.Name == name);
  933. var solution = solutionComp.Solution;
  934. solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
  935. // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
  936. // We want the reagents from the prototype to exist even if something else already created the solution.
  937. if (prototype is { Volume.Value: > 0 })
  938. solution.AddSolution(prototype, PrototypeManager);
  939. Dirty(solutionId, solutionComp);
  940. }
  941. if (needsInit)
  942. EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID);
  943. solutionEntity = (solutionId, solutionComp);
  944. return true;
  945. }
  946. private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
  947. {
  948. existed = true;
  949. var (uid, container) = entity;
  950. if (!Resolve(uid, ref container, logMissing: false))
  951. {
  952. container = AddComp<SolutionContainerManagerComponent>(uid);
  953. existed = false;
  954. }
  955. if (container.Solutions is null)
  956. container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity);
  957. if (!container.Solutions.TryGetValue(name, out var solution))
  958. {
  959. solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
  960. container.Solutions.Add(name, solution);
  961. existed = false;
  962. }
  963. else
  964. solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
  965. Dirty(uid, container);
  966. return solution;
  967. }
  968. private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
  969. {
  970. var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
  971. var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
  972. var solution = new SolutionComponent() { Solution = prototype };
  973. AddComp(uid, solution);
  974. var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
  975. AddComp(uid, relation);
  976. MetaDataSys.SetEntityName(uid, $"solution - {name}");
  977. ContainerSystem.Insert(uid, container, force: true);
  978. return (uid, solution, relation);
  979. }
  980. public void AdjustDissolvedReagent(
  981. Entity<SolutionComponent> dissolvedSolution,
  982. FixedPoint2 volume,
  983. ReagentId reagent,
  984. float concentrationChange)
  985. {
  986. if (concentrationChange == 0)
  987. return;
  988. var dissolvedSol = dissolvedSolution.Comp.Solution;
  989. var amtChange =
  990. GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange));
  991. if (concentrationChange > 0)
  992. {
  993. dissolvedSol.AddReagent(reagent, amtChange);
  994. }
  995. else
  996. {
  997. dissolvedSol.RemoveReagent(reagent,amtChange);
  998. }
  999. UpdateChemicals(dissolvedSolution);
  1000. }
  1001. public FixedPoint2 GetReagentQuantityFromConcentration(Entity<SolutionComponent> dissolvedSolution,
  1002. FixedPoint2 volume,float concentration)
  1003. {
  1004. var dissolvedSol = dissolvedSolution.Comp.Solution;
  1005. if (volume == 0
  1006. || dissolvedSol.Volume == 0)
  1007. return 0;
  1008. return concentration * volume;
  1009. }
  1010. public float GetReagentConcentration(Entity<SolutionComponent> dissolvedSolution,
  1011. FixedPoint2 volume, ReagentId dissolvedReagent)
  1012. {
  1013. var dissolvedSol = dissolvedSolution.Comp.Solution;
  1014. if (volume == 0
  1015. || dissolvedSol.Volume == 0
  1016. || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
  1017. return 0;
  1018. return (float)dissolvedVol / volume.Float();
  1019. }
  1020. public FixedPoint2 ClampReagentAmountByConcentration(
  1021. Entity<SolutionComponent> dissolvedSolution,
  1022. FixedPoint2 volume,
  1023. ReagentId dissolvedReagent,
  1024. FixedPoint2 dissolvedReagentAmount,
  1025. float maxConcentration = 1f)
  1026. {
  1027. var dissolvedSol = dissolvedSolution.Comp.Solution;
  1028. if (volume == 0
  1029. || dissolvedSol.Volume == 0
  1030. || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
  1031. return 0;
  1032. volume *= maxConcentration;
  1033. dissolvedVol += dissolvedReagentAmount;
  1034. var overflow = volume - dissolvedVol;
  1035. if (overflow < 0)
  1036. dissolvedReagentAmount += overflow;
  1037. return dissolvedReagentAmount;
  1038. }
  1039. }