using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Content.Server.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Examine; using Content.Shared.FixedPoint; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Chemistry.EntitySystems { /// /// This event alerts system that the solution was changed /// public class SolutionChangedEvent : EntityEventArgs { } /// /// Part of Chemistry system deal with SolutionContainers /// [UsedImplicitly] public partial class SolutionContainerSystem : EntitySystem { [Dependency] private readonly SharedChemicalReactionSystem _chemistrySystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(InitSolution); SubscribeLocalEvent(OnExamineSolution); } private void InitSolution(EntityUid uid, SolutionContainerManagerComponent component, ComponentInit args) { foreach (var keyValue in component.Solutions) { var solutionHolder = keyValue.Value; if (solutionHolder.MaxVolume == FixedPoint2.Zero) { solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume ? solutionHolder.TotalVolume : solutionHolder.InitialMaxVolume; } UpdateAppearance(uid, solutionHolder); } } private void OnExamineSolution(EntityUid uid, ExaminableSolutionComponent examinableComponent, ExaminedEvent args) { SolutionContainerManagerComponent? solutionsManager = null; if (!Resolve(args.Examined, ref solutionsManager) || !solutionsManager.Solutions.TryGetValue(examinableComponent.Solution, out var solutionHolder)) return; if (solutionHolder.Contents.Count == 0) { args.PushText(Loc.GetString("shared-solution-container-component-on-examine-empty-container")); return; } var primaryReagent = solutionHolder.GetPrimaryReagentId(); if (!_prototypeManager.TryIndex(primaryReagent, out ReagentPrototype? proto)) { Logger.Error( $"{nameof(Solution)} could not find the prototype associated with {primaryReagent}."); return; } var colorHex = solutionHolder.Color .ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable. var messageString = "shared-solution-container-component-on-examine-main-text"; args.PushMarkup(Loc.GetString(messageString, ("color", colorHex), ("wordedAmount", Loc.GetString(solutionHolder.Contents.Count == 1 ? "shared-solution-container-component-on-examine-worded-amount-one-reagent" : "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")), ("desc", Loc.GetString(proto.PhysicalDescription)))); } private void UpdateAppearance(EntityUid uid, Solution solution, AppearanceComponent? appearanceComponent = null) { if (!EntityManager.EntityExists(uid) || !Resolve(uid, ref appearanceComponent, false)) return; var filledVolumeFraction = solution.CurrentVolume.Float() / solution.MaxVolume.Float(); appearanceComponent.SetData(SolutionContainerVisuals.VisualState, new SolutionContainerVisualState(solution.Color, filledVolumeFraction)); } /// /// Removes part of the solution in the container. /// /// /// /// the volume of solution to remove. /// The solution that was removed. public Solution SplitSolution(EntityUid targetUid, Solution solutionHolder, FixedPoint2 quantity) { var splitSol = solutionHolder.SplitSolution(quantity); UpdateChemicals(targetUid, solutionHolder); return splitSol; } private void UpdateChemicals(EntityUid uid, Solution solutionHolder, bool needsReactionsProcessing = false) { // Process reactions if (needsReactionsProcessing && solutionHolder.CanReact) { _chemistrySystem.FullyReactSolution(solutionHolder, uid, solutionHolder.MaxVolume); } UpdateAppearance(uid, solutionHolder); RaiseLocalEvent(uid, new SolutionChangedEvent()); } public void RemoveAllSolution(EntityUid uid, Solution solutionHolder) { if (solutionHolder.CurrentVolume == 0) return; solutionHolder.RemoveAllSolution(); UpdateChemicals(uid, solutionHolder); } public void RemoveAllSolution(EntityUid uid, SolutionContainerManagerComponent? solutionContainerManager = null) { if (!Resolve(uid, ref solutionContainerManager)) return; foreach (var solution in solutionContainerManager.Solutions.Values) { RemoveAllSolution(uid, solution); } } /// /// Sets the capacity (maximum volume) of a solution to a new value. /// /// The entity containing the solution. /// The solution to set the capacity of. /// The value to set the capacity of the solution to. public void SetCapacity(EntityUid targetUid, Solution targetSolution, FixedPoint2 capacity) { if (targetSolution.MaxVolume == capacity) return; targetSolution.MaxVolume = capacity; if (capacity < targetSolution.CurrentVolume) targetSolution.RemoveSolution(targetSolution.CurrentVolume - capacity); UpdateChemicals(targetUid, targetSolution); } /// /// Adds reagent of an Id to the container. /// /// /// Container to which we are adding reagent /// The Id of the reagent to add. /// The amount of reagent to add. /// The amount of reagent successfully added. /// If all the reagent could be added. public bool TryAddReagent(EntityUid targetUid, Solution targetSolution, string reagentId, FixedPoint2 quantity, out FixedPoint2 acceptedQuantity, float? temperature = null) { acceptedQuantity = targetSolution.AvailableVolume > quantity ? quantity : targetSolution.AvailableVolume; targetSolution.AddReagent(reagentId, acceptedQuantity, temperature); if (acceptedQuantity > 0) UpdateChemicals(targetUid, targetSolution, true); return acceptedQuantity == quantity; } /// /// Removes reagent of an Id to the container. /// /// /// Solution container from which we are removing reagent /// The Id of the reagent to remove. /// The amount of reagent to remove. /// If the reagent to remove was found in the container. public bool TryRemoveReagent(EntityUid targetUid, Solution? container, string reagentId, FixedPoint2 quantity) { if (container == null || !container.ContainsReagent(reagentId)) return false; container.RemoveReagent(reagentId, quantity); UpdateChemicals(targetUid, container); return true; } /// /// Adds a solution to the container, if it can fully fit. /// /// /// The container to which we try to add. /// The solution to try to add. /// If the solution could be added. public bool TryAddSolution(EntityUid targetUid, Solution? targetSolution, Solution solution) { if (targetSolution == null || !targetSolution.CanAddSolution(solution) || solution.TotalVolume == 0) return false; targetSolution.AddSolution(solution); UpdateChemicals(targetUid, targetSolution, true); return true; } public bool TryGetSolution(EntityUid uid, string name, [NotNullWhen(true)] out Solution? solution, SolutionContainerManagerComponent? solutionsMgr = null) { if (!Resolve(uid, ref solutionsMgr, false)) { solution = null; return false; } return solutionsMgr.Solutions.TryGetValue(name, out solution); } /// /// Will ensure a solution is added to given entity even if it's missing solutionContainerManager /// /// EntityUid to which to add solution /// name for the solution /// solution components used in resolves /// solution public Solution EnsureSolution(EntityUid uid, string name, SolutionContainerManagerComponent? solutionsMgr = null) { if (!Resolve(uid, ref solutionsMgr, false)) { solutionsMgr = EntityManager.EnsureComponent(uid); } if (!solutionsMgr.Solutions.ContainsKey(name)) { var newSolution = new Solution(); solutionsMgr.Solutions.Add(name, newSolution); } return solutionsMgr.Solutions[name]; } public string[] RemoveEachReagent(EntityUid uid, Solution solution, FixedPoint2 quantity) { var removedReagent = new string[solution.Contents.Count]; if (quantity <= 0) return Array.Empty(); var pos = 0; for (var i = 0; i < solution.Contents.Count; i++) { var (reagentId, curQuantity) = solution.Contents[i]; removedReagent[pos++] = reagentId; if (!_prototypeManager.TryIndex(reagentId, out ReagentPrototype? proto)) proto = new ReagentPrototype(); var newQuantity = curQuantity - quantity; if (newQuantity <= 0) { solution.Contents.RemoveSwap(i); solution.TotalVolume -= curQuantity; } else { solution.Contents[i] = new Solution.ReagentQuantity(reagentId, newQuantity); solution.TotalVolume -= quantity; } } UpdateChemicals(uid, solution); return removedReagent; } public void TryRemoveAllReagents(EntityUid uid, Solution solution, List removeReagents) { if (removeReagents.Count == 0) return; foreach (var reagent in removeReagents) { solution.RemoveReagent(reagent.ReagentId, reagent.Quantity); } UpdateChemicals(uid, solution); } public FixedPoint2 GetReagentQuantity(EntityUid owner, string reagentId) { var reagentQuantity = FixedPoint2.New(0); if (EntityManager.EntityExists(owner) && EntityManager.TryGetComponent(owner, out SolutionContainerManagerComponent? managerComponent)) { foreach (var solution in managerComponent.Solutions.Values) { reagentQuantity += solution.GetReagentQuantity(reagentId); } } return reagentQuantity; } // Thermal energy and temperature management. #region Thermal Energy and Temperature /// /// Sets the temperature of a solution to a new value and then checks for reaction processing. /// /// The entity in which the solution is located. /// The solution to set the temperature of. /// The new value to set the temperature to. public void SetTemperature(EntityUid owner, Solution solution, float temperature) { if (temperature == solution.Temperature) return; solution.Temperature = temperature; UpdateChemicals(owner, solution, true); } /// /// Sets the thermal energy of a solution to a new value and then checks for reaction processing. /// /// The entity in which the solution is located. /// The solution to set the thermal energy of. /// The new value to set the thermal energy to. public void SetThermalEnergy(EntityUid owner, Solution solution, float thermalEnergy) { if (thermalEnergy == solution.ThermalEnergy) return; solution.ThermalEnergy = thermalEnergy; UpdateChemicals(owner, solution, true); } /// /// Adds some thermal energy to a solution and then checks for reaction processing. /// /// The entity in which the solution is located. /// The solution to set the thermal energy of. /// The new value to set the thermal energy to. public void AddThermalEnergy(EntityUid owner, Solution solution, float thermalEnergy) { if (thermalEnergy == 0.0f) return; solution.ThermalEnergy += thermalEnergy; UpdateChemicals(owner, solution, true); } #endregion Thermal Energy and Temperature } }