diff --git a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs index fba1b00ec1..7e46cb0ab2 100644 --- a/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs +++ b/Content.Client/Administration/UI/ManageSolutions/EditSolutionsWindow.xaml.cs @@ -82,7 +82,7 @@ namespace Content.Client.Administration.UI.ManageSolutions volumeLabel.HorizontalExpand = true; volumeLabel.Margin = new Thickness(0, 4); volumeLabel.Text = Loc.GetString("admin-solutions-window-volume-label", - ("currentVolume", solution.CurrentVolume), + ("currentVolume", solution.Volume), ("maxVolume", solution.MaxVolume)); var capacityBox = new BoxContainer(); @@ -116,16 +116,16 @@ namespace Content.Client.Administration.UI.ManageSolutions private void UpdateThermalBox(Solution solution) { ThermalBox.DisposeAllChildren(); - + var heatCap = solution.GetHeatCapacity(null); var specificHeatLabel = new Label(); specificHeatLabel.HorizontalExpand = true; specificHeatLabel.Margin = new Thickness(0, 1); - specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", solution.SpecificHeat.ToString("G3"))); + specificHeatLabel.Text = Loc.GetString("admin-solutions-window-specific-heat-label", ("specificHeat", heatCap.ToString("G3"))); var heatCapacityLabel = new Label(); heatCapacityLabel.HorizontalExpand = true; heatCapacityLabel.Margin = new Thickness(0, 1); - heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", solution.HeatCapacity.ToString("G3"))); + heatCapacityLabel.Text = Loc.GetString("admin-solutions-window-heat-capacity-label", ("heatCapacity", (heatCap/solution.Volume.Float()).ToString("G3"))); // Temperature entry: var temperatureBox = new BoxContainer(); @@ -161,7 +161,7 @@ namespace Content.Client.Administration.UI.ManageSolutions var thermalEnergySpin = new FloatSpinBox(1, 2); thermalEnergySpin.HorizontalExpand = true; thermalEnergySpin.Margin = new Thickness(0, 1); - thermalEnergySpin.Value = solution.ThermalEnergy; + thermalEnergySpin.Value = solution.Temperature * heatCap; thermalEnergySpin.OnValueChanged += SetThermalEnergy; thermalEnergyBox.AddChild(thermalEnergyLabel); diff --git a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs index f9cbe9e9dd..5e9b15a6c2 100644 --- a/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Chemistry/SolutionSystemTests.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; using NUnit.Framework; using Robust.Shared.GameObjects; -using Robust.Shared.Map; +using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Chemistry; @@ -24,6 +24,25 @@ public sealed class SolutionSystemTests solutions: beaker: maxVol: 50 + +- type: reagent + id: TestReagentA + name: nah + desc: nah + physicalDesc: nah + +- type: reagent + id: TestReagentB + name: nah + desc: nah + physicalDesc: nah + +- type: reagent + id: TestReagentC + specificHeat: 2.0 + name: nah + desc: nah + physicalDesc: nah "; [Test] public async Task TryAddTwoNonReactiveReagent() @@ -32,6 +51,7 @@ public sealed class SolutionSystemTests var server = pairTracker.Pair.Server; var entityManager = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); var containerSystem = entityManager.EntitySysManager.GetEntitySystem(); var testMap = await PoolManager.CreateTestMap(pairTracker); var coordinates = testMap.GridCoords; @@ -50,12 +70,12 @@ public sealed class SolutionSystemTests Assert.That(containerSystem .TryGetSolution(beaker, "beaker", out var solution)); - solution.AddSolution(originalWater); + solution.AddSolution(originalWater, protoMan); Assert.That(containerSystem .TryAddSolution(beaker, solution, oilAdded)); - solution.ContainsReagent("Water", out var water); - solution.ContainsReagent("Oil", out var oil); + solution.TryGetReagent("Water", out var water); + solution.TryGetReagent("Oil", out var oil); Assert.That(water, Is.EqualTo(waterQuantity)); Assert.That(oil, Is.EqualTo(oilQuantity)); }); @@ -74,6 +94,7 @@ public sealed class SolutionSystemTests var testMap = await PoolManager.CreateTestMap(pairTracker); var entityManager = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); var containerSystem = entityManager.EntitySysManager.GetEntitySystem(); var coordinates = testMap.GridCoords; @@ -91,12 +112,12 @@ public sealed class SolutionSystemTests Assert.That(containerSystem .TryGetSolution(beaker, "beaker", out var solution)); - solution.AddSolution(originalWater); + solution.AddSolution(originalWater, protoMan); Assert.That(containerSystem .TryAddSolution(beaker, solution, oilAdded), Is.False); - solution.ContainsReagent("Water", out var water); - solution.ContainsReagent("Oil", out var oil); + solution.TryGetReagent("Water", out var water); + solution.TryGetReagent("Oil", out var oil); Assert.That(water, Is.EqualTo(waterQuantity)); Assert.That(oil, Is.EqualTo(FixedPoint2.Zero)); }); @@ -113,13 +134,14 @@ public sealed class SolutionSystemTests var entityManager = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); var testMap = await PoolManager.CreateTestMap(pairTracker); var containerSystem = entityManager.EntitySysManager.GetEntitySystem(); var coordinates = testMap.GridCoords; EntityUid beaker; - await server.WaitAssertion(() => + await server.WaitAssertion((System.Action)(() => { int ratio = 9; int threshold = 20; @@ -133,22 +155,22 @@ public sealed class SolutionSystemTests Assert.That(containerSystem .TryGetSolution(beaker, "beaker", out var solution)); - solution.AddSolution(originalWater); + solution.AddSolution(originalWater, protoMan); Assert.That(containerSystem .TryMixAndOverflow(beaker, solution, oilAdded, threshold, out var overflowingSolution)); - Assert.That(solution.CurrentVolume, Is.EqualTo(FixedPoint2.New(threshold))); - solution.ContainsReagent("Water", out var waterMix); - solution.ContainsReagent("Oil", out var oilMix); + Assert.That((FixedPoint2) solution.Volume, Is.EqualTo(FixedPoint2.New(threshold))); + solution.TryGetReagent("Water", out var waterMix); + solution.TryGetReagent("Oil", out var oilMix); Assert.That(waterMix, Is.EqualTo(FixedPoint2.New(threshold / (ratio + 1)))); Assert.That(oilMix, Is.EqualTo(FixedPoint2.New(threshold / (ratio + 1) * ratio))); - Assert.That(overflowingSolution.CurrentVolume, Is.EqualTo(FixedPoint2.New(80))); - overflowingSolution.ContainsReagent("Water", out var waterOverflow); - overflowingSolution.ContainsReagent("Oil", out var oilOverFlow); + Assert.That((FixedPoint2) overflowingSolution.Volume, Is.EqualTo(FixedPoint2.New(80))); + overflowingSolution.TryGetReagent("Water", out var waterOverflow); + overflowingSolution.TryGetReagent("Oil", out var oilOverFlow); Assert.That(waterOverflow, Is.EqualTo(waterQuantity - waterMix)); Assert.That(oilOverFlow, Is.EqualTo(oilQuantity - oilMix)); - }); + })); await pairTracker.CleanReturnAsync(); } @@ -161,6 +183,7 @@ public sealed class SolutionSystemTests var server = pairTracker.Pair.Server; var entityManager = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); var containerSystem = entityManager.EntitySysManager.GetEntitySystem(); var testMap = await PoolManager.CreateTestMap(pairTracker); var coordinates = testMap.GridCoords; @@ -181,7 +204,7 @@ public sealed class SolutionSystemTests Assert.That(containerSystem .TryGetSolution(beaker, "beaker", out var solution)); - solution.AddSolution(originalWater); + solution.AddSolution(originalWater, protoMan); Assert.That(containerSystem .TryMixAndOverflow(beaker, solution, oilAdded, threshold, out _), Is.False); @@ -189,4 +212,43 @@ public sealed class SolutionSystemTests await pairTracker.CleanReturnAsync(); } + + [Test] + public async Task TestTemperatureCalculations() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes }); + var server = pairTracker.Pair.Server; + var protoMan = server.ResolveDependency(); + const float temp = 100.0f; + + // Adding reagent with adjusts temperature + await server.WaitAssertion(() => + { + + var solution = new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp }; + Assert.That(solution.Temperature, Is.EqualTo(temp * 1)); + + solution.AddSolution(new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp * 3 }, protoMan); + Assert.That(solution.Temperature, Is.EqualTo(temp * 2)); + + solution.AddSolution(new Solution("TestReagentB", FixedPoint2.New(100)) { Temperature = temp * 5 }, protoMan); + Assert.That(solution.Temperature, Is.EqualTo(temp * 3)); + }); + + // adding solutions combines thermal energy + await server.WaitAssertion(() => + { + var solutionOne = new Solution("TestReagentA", FixedPoint2.New(100)) { Temperature = temp }; + + var solutionTwo = new Solution("TestReagentB", FixedPoint2.New(100)) { Temperature = temp }; + solutionTwo.AddReagent("TestReagentC", FixedPoint2.New(100)); + + var thermalEnergyOne = solutionOne.GetHeatCapacity(protoMan) * solutionOne.Temperature; + var thermalEnergyTwo = solutionTwo.GetHeatCapacity(protoMan) * solutionTwo.Temperature; + solutionOne.AddSolution(solutionTwo, protoMan); + Assert.That(solutionOne.GetHeatCapacity(protoMan) * solutionOne.Temperature, Is.EqualTo(thermalEnergyOne + thermalEnergyTwo)); + }); + + await pairTracker.CleanReturnAsync(); + } } diff --git a/Content.Server/Administration/Commands/SetSolutionThermalEnergy.cs b/Content.Server/Administration/Commands/SetSolutionThermalEnergy.cs index e2510a2884..6c65a9c2b6 100644 --- a/Content.Server/Administration/Commands/SetSolutionThermalEnergy.cs +++ b/Content.Server/Administration/Commands/SetSolutionThermalEnergy.cs @@ -46,7 +46,7 @@ namespace Content.Server.Administration.Commands return; } - if (solution.HeatCapacity <= 0.0f) + if (solution.GetHeatCapacity(null) <= 0.0f) { if(quantity != 0.0f) { diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Server/Animals/Systems/UdderSystem.cs index d24cdd7cb7..20b5fbdf23 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Server/Animals/Systems/UdderSystem.cs @@ -98,7 +98,7 @@ namespace Content.Server.Animals.Systems if (!_solutionContainerSystem.TryGetRefillableSolution(ev.ContainerUid, out var targetSolution)) return; - var quantity = solution.TotalVolume; + var quantity = solution.Volume; if(quantity == 0) { _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, ev.UserUid); diff --git a/Content.Server/Body/Components/StomachComponent.cs b/Content.Server/Body/Components/StomachComponent.cs index 39970b9c1d..32b0b4adb7 100644 --- a/Content.Server/Body/Components/StomachComponent.cs +++ b/Content.Server/Body/Components/StomachComponent.cs @@ -23,8 +23,8 @@ namespace Content.Server.Body.Components /// /// Initial internal solution storage volume /// - [DataField("maxVolume")] - public FixedPoint2 InitialMaxVolume { get; private set; } = FixedPoint2.New(50); + [DataField("initialMaxVolume", readOnly: true)] + public readonly FixedPoint2 InitialMaxVolume = FixedPoint2.New(50); /// /// Time in seconds between reagents being ingested and them being diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index dc652c0f61..5dd004ffad 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -96,7 +96,7 @@ public sealed class BloodstreamSystem : EntitySystem continue; // First, let's refresh their blood if possible. - if (bloodstream.BloodSolution.CurrentVolume < bloodstream.BloodSolution.MaxVolume) + if (bloodstream.BloodSolution.Volume < bloodstream.BloodSolution.MaxVolume) TryModifyBloodLevel(uid, bloodstream.BloodRefreshAmount, bloodstream); // Next, let's remove some blood from them according to their bleed level. @@ -258,7 +258,7 @@ public sealed class BloodstreamSystem : EntitySystem if (!Resolve(uid, ref component)) return 0.0f; - return (component.BloodSolution.CurrentVolume / component.BloodSolution.MaxVolume).Float(); + return component.BloodSolution.FillFraction; } public void SetBloodLossThreshold(EntityUid uid, float threshold, BloodstreamComponent? comp = null) @@ -284,13 +284,13 @@ public sealed class BloodstreamSystem : EntitySystem // since we also wanna handle moving it to the temporary solution // and then spilling it if necessary. var newSol = component.BloodSolution.SplitSolution(-amount); - component.BloodTemporarySolution.AddSolution(newSol); + component.BloodTemporarySolution.AddSolution(newSol, _prototypeManager); - if (component.BloodTemporarySolution.CurrentVolume > component.BleedPuddleThreshold) + if (component.BloodTemporarySolution.Volume > component.BleedPuddleThreshold) { // Pass some of the chemstream into the spilled blood. - var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.CurrentVolume / 10); - component.BloodTemporarySolution.AddSolution(temp); + var temp = component.ChemicalSolution.SplitSolution(component.BloodTemporarySolution.Volume / 10); + component.BloodTemporarySolution.AddSolution(temp, _prototypeManager); _spillableSystem.SpillAt(uid, component.BloodTemporarySolution, "PuddleBlood", false); component.BloodTemporarySolution.RemoveAllSolution(); } @@ -324,11 +324,11 @@ public sealed class BloodstreamSystem : EntitySystem component.ChemicalSolution.MaxVolume; var tempSol = new Solution() { MaxVolume = max }; - tempSol.AddSolution(component.BloodSolution); + tempSol.AddSolution(component.BloodSolution, _prototypeManager); component.BloodSolution.RemoveAllSolution(); - tempSol.AddSolution(component.BloodTemporarySolution); + tempSol.AddSolution(component.BloodTemporarySolution, _prototypeManager); component.BloodTemporarySolution.RemoveAllSolution(); - tempSol.AddSolution(component.ChemicalSolution); + tempSol.AddSolution(component.ChemicalSolution, _prototypeManager); component.ChemicalSolution.RemoveAllSolution(); _spillableSystem.SpillAt(uid, tempSol, "PuddleBlood", true); } diff --git a/Content.Server/Body/Systems/StomachSystem.cs b/Content.Server/Body/Systems/StomachSystem.cs index 2bc716ce14..36f02c0b82 100644 --- a/Content.Server/Body/Systems/StomachSystem.cs +++ b/Content.Server/Body/Systems/StomachSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Components; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Body.Organ; @@ -47,7 +47,7 @@ namespace Content.Server.Body.Systems delta.Increment(stomach.UpdateInterval); if (delta.Lifetime > stomach.DigestionDelay) { - if (stomachSolution.ContainsReagent(delta.ReagentId, out var quant)) + if (stomachSolution.TryGetReagent(delta.ReagentId, out var quant)) { if (quant > delta.Quantity) quant = delta.Quantity; @@ -89,8 +89,7 @@ namespace Content.Server.Body.Systems private void OnComponentInit(EntityUid uid, StomachComponent component, ComponentInit args) { - var solution = _solutionContainerSystem.EnsureSolution(uid, DefaultSolutionName); - solution.MaxVolume = component.InitialMaxVolume; + _solutionContainerSystem.EnsureSolution(uid, DefaultSolutionName, component.InitialMaxVolume, out _); } public bool CanTransferSolution(EntityUid uid, Solution solution, diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs index 552b60a3fb..bc9bfbad8b 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Produce.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs @@ -1,4 +1,5 @@ using Content.Server.Botany.Components; +using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; using Robust.Server.GameObjects; @@ -17,17 +18,17 @@ public sealed partial class BotanySystem sprite.LayerSetState(0, seed.PlantIconState); } - var solutionContainer = _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName); - - solutionContainer.RemoveAllSolution(); + Solution.ReagentQuantity[] reagents = new Solution.ReagentQuantity[seed.Chemicals.Count]; + int i = 0; foreach (var (chem, quantity) in seed.Chemicals) { var amount = FixedPoint2.New(quantity.Min); if (quantity.PotencyDivisor > 0 && seed.Potency > 0) amount += FixedPoint2.New(seed.Potency / quantity.PotencyDivisor); amount = FixedPoint2.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max)); - solutionContainer.MaxVolume += amount; - solutionContainer.AddReagent(chem, amount); + reagents[i++] = new(chem, amount); } + + _solutionContainerSystem.EnsureSolution(uid, produce.SolutionName, reagents); } } diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index c8f3e144e4..6091b98a53 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -211,7 +211,7 @@ namespace Content.Server.Botany.Systems var split =_solutionSystem.Drain(solutionEntity, solution, amount); - if (split.TotalVolume == 0) + if (split.Volume == 0) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", ("owner", args.Used)), args.User); @@ -220,7 +220,7 @@ namespace Content.Server.Botany.Systems _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-spray-message", ("owner", uid), - ("amount", split.TotalVolume)), args.User, PopupType.Medium); + ("amount", split.Volume)), args.User, PopupType.Medium); _solutionSystem.TryAddSolution(targetEntity, targetSolution, split); @@ -284,7 +284,7 @@ namespace Content.Server.Botany.Systems { // This deliberately discards overfill. _solutionSystem.TryAddSolution(args.Used, solution2, - _solutionSystem.SplitSolution(args.Used, solution2, solution2.TotalVolume)); + _solutionSystem.SplitSolution(args.Used, solution2, solution2.Volume)); ForceUpdateByExternalCause(uid, component); } @@ -780,7 +780,7 @@ namespace Content.Server.Botany.Systems if (!_solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var solution)) return; - if (solution.TotalVolume > 0 && component.MutationLevel < 25) + if (solution.Volume > 0 && component.MutationLevel < 25) { var amt = FixedPoint2.New(1); foreach (var (reagentId, quantity) in _solutionSystem.RemoveEachReagent(uid, solution, amt)) diff --git a/Content.Server/Chemistry/Components/FoamSolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/FoamSolutionAreaEffectComponent.cs index 0d37203f31..380f92caac 100644 --- a/Content.Server/Chemistry/Components/FoamSolutionAreaEffectComponent.cs +++ b/Content.Server/Chemistry/Components/FoamSolutionAreaEffectComponent.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Administration.Logs; @@ -6,6 +6,7 @@ using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Foam; using Content.Shared.Inventory; +using Robust.Shared.Prototypes; namespace Content.Server.Chemistry.Components { @@ -14,6 +15,7 @@ namespace Content.Server.Chemistry.Components public sealed class FoamSolutionAreaEffectComponent : SolutionAreaEffectComponent { [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; public new const string SolutionName = "solutionArea"; @@ -25,7 +27,7 @@ namespace Content.Server.Chemistry.Components if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance) && EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)) { - appearance.SetData(FoamVisuals.Color, solution.Color.WithAlpha(0.80f)); + appearance.SetData(FoamVisuals.Color, solution.GetColor(_proto).WithAlpha(0.80f)); } } @@ -60,7 +62,7 @@ namespace Content.Server.Chemistry.Components var bloodstreamSys = EntitySystem.Get(); var cloneSolution = solution.Clone(); - var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection), + var transferAmount = FixedPoint2.Min(cloneSolution.Volume * solutionFraction * (1 - protection), bloodstream.ChemicalSolution.AvailableVolume); var transferSolution = cloneSolution.SplitSolution(transferAmount); diff --git a/Content.Server/Chemistry/Components/HyposprayComponent.cs b/Content.Server/Chemistry/Components/HyposprayComponent.cs index 9463521347..6b9ff6cb1c 100644 --- a/Content.Server/Chemistry/Components/HyposprayComponent.cs +++ b/Content.Server/Chemistry/Components/HyposprayComponent.cs @@ -25,7 +25,7 @@ namespace Content.Server.Chemistry.Components { var solutionSys = _entMan.EntitySysManager.GetEntitySystem(); return solutionSys.TryGetSolution(Owner, SolutionName, out var solution) - ? new HyposprayComponentState(solution.CurrentVolume, solution.MaxVolume) + ? new HyposprayComponentState(solution.Volume, solution.MaxVolume) : new HyposprayComponentState(FixedPoint2.Zero, FixedPoint2.Zero); } } diff --git a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs index 9dc05769ca..2aae1508aa 100644 --- a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs +++ b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; using Content.Shared.Administration.Logs; @@ -7,6 +7,7 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Smoking; +using Robust.Shared.Prototypes; namespace Content.Server.Chemistry.Components { @@ -15,6 +16,7 @@ namespace Content.Server.Chemistry.Components public sealed class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent { [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; public new const string SolutionName = "solutionArea"; @@ -24,7 +26,7 @@ namespace Content.Server.Chemistry.Components if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance) && EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solution)) { - appearance.SetData(SmokeVisuals.Color, solution.Color); + appearance.SetData(SmokeVisuals.Color, solution.GetColor(_proto)); } } @@ -42,7 +44,7 @@ namespace Content.Server.Chemistry.Components var chemistry = EntitySystem.Get(); var cloneSolution = solution.Clone(); - var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.ChemicalSolution.AvailableVolume); + var transferAmount = FixedPoint2.Min(cloneSolution.Volume * solutionFraction, bloodstream.ChemicalSolution.AvailableVolume); var transferSolution = cloneSolution.SplitSolution(transferAmount); foreach (var reagentQuantity in transferSolution.Contents.ToArray()) diff --git a/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs index 59a915b63d..459c07dd60 100644 --- a/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs @@ -194,14 +194,14 @@ namespace Content.Server.Chemistry.Components public void TryAddSolution(Solution solution) { - if (solution.TotalVolume == 0) + if (solution.Volume == 0) return; if (!EntitySystem.Get().TryGetSolution(Owner, SolutionName, out var solutionArea)) return; var addSolution = - solution.SplitSolution(FixedPoint2.Min(solution.TotalVolume, solutionArea.AvailableVolume)); + solution.SplitSolution(FixedPoint2.Min(solution.Volume, solutionArea.AvailableVolume)); EntitySystem.Get().TryAddSolution(Owner, solutionArea, addSolution); diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs index 966cf8b6e9..45d4118c1a 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -66,7 +66,7 @@ namespace Content.Server.Chemistry.EntitySystems var outputContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.OutputSlotName); var bufferReagents = bufferSolution.Contents; - var bufferCurrentVolume = bufferSolution.CurrentVolume; + var bufferCurrentVolume = bufferSolution.Volume; var state = new ChemMasterBoundUserInterfaceState( chemMaster.Mode, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer), @@ -288,7 +288,7 @@ namespace Content.Server.Chemistry.EntitySystems return false; } - if (solution.TotalVolume == 0) + if (solution.Volume == 0) { if (user.HasValue) _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-empty-text"), user.Value); @@ -296,7 +296,7 @@ namespace Content.Server.Chemistry.EntitySystems } // ReSharper disable once InvertIf - if (neededVolume > solution.CurrentVolume) + if (neededVolume > solution.Volume) { if (user.HasValue) _popupSystem.PopupCursor(Loc.GetString("chem-master-window-buffer-low-text"), user.Value); @@ -343,12 +343,12 @@ namespace Content.Server.Chemistry.EntitySystems if (!TryComp(container, out ServerStorageComponent? storage)) return null; - var pills = storage.Storage?.ContainedEntities.Select(pill => + var pills = storage.Storage?.ContainedEntities.Select((Func) (pill => { _solutionContainerSystem.TryGetSolution(pill, SharedChemMaster.PillSolutionName, out var solution); - var quantity = solution?.CurrentVolume ?? FixedPoint2.Zero; - return (Name(pill), quantity); - }).ToList(); + var quantity = solution?.Volume ?? FixedPoint2.Zero; + return ((string, FixedPoint2 quantity))(Name(pill), quantity:(FixedPoint2) quantity); + })).ToList(); return pills is null ? null @@ -360,7 +360,7 @@ namespace Content.Server.Chemistry.EntitySystems var reagents = solution.Contents .Select(reagent => (reagent.ReagentId, reagent.Quantity)).ToList(); - return new ContainerInfo(name, true, solution.CurrentVolume, solution.MaxVolume, reagents); + return new ContainerInfo(name, true, solution.Volume, solution.MaxVolume, reagents); } } } diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs index 0d4606d4ee..5062dbc7c1 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs @@ -145,7 +145,7 @@ public sealed partial class ChemistrySystem { _solutions.TryGetSolution(uid, InjectorComponent.SolutionName, out var solution); - var currentVolume = solution?.CurrentVolume ?? FixedPoint2.Zero; + var currentVolume = solution?.Volume ?? FixedPoint2.Zero; var maxVolume = solution?.MaxVolume ?? FixedPoint2.Zero; args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState); @@ -323,7 +323,7 @@ public sealed partial class ChemistrySystem removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection); _popup.PopupEntity(Loc.GetString("injector-component-inject-success-message", - ("amount", removedSolution.TotalVolume), + ("amount", removedSolution.Volume), ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, user); Dirty(component); @@ -333,7 +333,7 @@ public sealed partial class ChemistrySystem private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill) { if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) - || solution.CurrentVolume == 0) + || solution.Volume == 0) { return; } @@ -363,7 +363,7 @@ public sealed partial class ChemistrySystem } _popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message", - ("amount", removedSolution.TotalVolume), + ("amount", removedSolution.Volume), ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user); Dirty(component); @@ -374,7 +374,7 @@ public sealed partial class ChemistrySystem { // Automatically set syringe to draw after completely draining it. if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) - && solution.CurrentVolume == 0) + && solution.Volume == 0) { component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw; } @@ -399,7 +399,7 @@ public sealed partial class ChemistrySystem } // Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector - var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.DrawAvailable, solution.AvailableVolume); + var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.Volume, solution.AvailableVolume); if (realTransferAmount <= 0) { @@ -424,7 +424,7 @@ public sealed partial class ChemistrySystem } _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", - ("amount", removedSolution.TotalVolume), + ("amount", removedSolution.Volume), ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user); Dirty(component); @@ -436,7 +436,7 @@ public sealed partial class ChemistrySystem var drawAmount = (float) transferAmount; var bloodAmount = drawAmount; var chemAmount = 0f; - if (stream.ChemicalSolution.CurrentVolume > 0f) // If they have stuff in their chem stream, we'll draw some of that + if (stream.ChemicalSolution.Volume > 0f) // If they have stuff in their chem stream, we'll draw some of that { bloodAmount = drawAmount * 0.85f; chemAmount = drawAmount * 0.15f; diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs index 9a040bf652..317a3c26ab 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystemHypospray.cs @@ -77,7 +77,7 @@ namespace Content.Server.Chemistry.EntitySystems _solutions.TryGetSolution(uid, component.SolutionName, out var hypoSpraySolution); - if (hypoSpraySolution == null || hypoSpraySolution.CurrentVolume == 0) + if (hypoSpraySolution == null || hypoSpraySolution.Volume == 0) { _popup.PopupCursor(Loc.GetString("hypospray-component-empty-message"), user); return true; diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs index bebb0a246d..12979cccb3 100644 --- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Chemistry.Dispenser; using Content.Shared.Containers.ItemSlots; using Content.Shared.Database; using Content.Shared.Emag.Systems; +using Content.Shared.FixedPoint; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -62,7 +63,7 @@ namespace Content.Server.Chemistry.EntitySystems if (_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var solution)) { var reagents = solution.Contents.Select(reagent => (reagent.ReagentId, reagent.Quantity)).ToList(); - return new ContainerInfo(Name(container.Value), true, solution.CurrentVolume, solution.MaxVolume, reagents); + return new ContainerInfo(Name(container.Value), true, solution.Volume, solution.MaxVolume, reagents); } return null; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs index 13d11b13f2..0e47881c84 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.Capabilities.cs @@ -110,7 +110,7 @@ public sealed partial class SolutionContainerSystem { return !TryGetDrainableSolution(uid, out var solution) ? FixedPoint2.Zero - : solution.CurrentVolume; + : solution.Volume; } public float PercentFull(EntityUid uid) @@ -118,7 +118,7 @@ public sealed partial class SolutionContainerSystem if (!TryGetDrainableSolution(uid, out var solution) || solution.MaxVolume.Equals(FixedPoint2.Zero)) return 0; - return ((solution.CurrentVolume.Float() / solution.MaxVolume.Float()) * 100); + return solution.FillFraction * 100; } public bool TryGetFitsInDispenser(EntityUid owner, diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs index 53d8f0e8a4..9909973cce 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -51,13 +51,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem foreach (var (name, solutionHolder) in component.Solutions) { solutionHolder.Name = name; - if (solutionHolder.MaxVolume == FixedPoint2.Zero) - { - solutionHolder.MaxVolume = solutionHolder.TotalVolume > solutionHolder.InitialMaxVolume - ? solutionHolder.TotalVolume - : solutionHolder.InitialMaxVolume; - } - + solutionHolder.ValidateSolution(); UpdateAppearance(uid, solutionHolder); } } @@ -70,14 +64,14 @@ public sealed partial class SolutionContainerSystem : EntitySystem || !solutionsManager.Solutions.TryGetValue(examinableComponent.Solution, out var solutionHolder)) return; - if (solutionHolder.Contents.Count == 0) + var primaryReagent = solutionHolder.GetPrimaryReagentId(); + + if (string.IsNullOrEmpty(primaryReagent)) { 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( @@ -85,7 +79,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem return; } - var colorHex = solutionHolder.Color + var colorHex = solutionHolder.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"; @@ -104,9 +98,9 @@ public sealed partial class SolutionContainerSystem : EntitySystem || !Resolve(uid, ref appearanceComponent, false)) return; - var filledVolumePercent = Math.Min(1.0f, solution.CurrentVolume.Float() / solution.MaxVolume.Float()); + var filledVolumePercent = solution.FillFraction * 100; appearanceComponent.SetData(SolutionContainerVisuals.VisualState, - new SolutionContainerVisualState(solution.Color, filledVolumePercent)); + new SolutionContainerVisualState(solution.GetColor(_prototypeManager), filledVolumePercent)); } /// @@ -139,7 +133,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem public void RemoveAllSolution(EntityUid uid, Solution solutionHolder) { - if (solutionHolder.CurrentVolume == 0) + if (solutionHolder.Volume == 0) return; solutionHolder.RemoveAllSolution(); @@ -169,8 +163,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem return; targetSolution.MaxVolume = capacity; - if (capacity < targetSolution.CurrentVolume) - targetSolution.RemoveSolution(targetSolution.CurrentVolume - capacity); + if (capacity < targetSolution.Volume) + targetSolution.RemoveSolution(targetSolution.Volume - capacity); UpdateChemicals(targetUid, targetSolution); } @@ -188,14 +182,20 @@ public sealed partial class SolutionContainerSystem : EntitySystem 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); + if (acceptedQuantity <= 0) + return quantity == 0; + if (temperature == null) + targetSolution.AddReagent(reagentId, acceptedQuantity); + else + targetSolution.AddReagent(_prototypeManager.Index(reagentId), acceptedQuantity, temperature.Value, _prototypeManager); + + UpdateChemicals(targetUid, targetSolution, true); return acceptedQuantity == quantity; } + /// /// Removes reagent of an Id to the container. /// @@ -224,10 +224,10 @@ public sealed partial class SolutionContainerSystem : EntitySystem public bool TryAddSolution(EntityUid targetUid, Solution? targetSolution, Solution addedSolution) { if (targetSolution == null - || !targetSolution.CanAddSolution(addedSolution) || addedSolution.TotalVolume == 0) + || !targetSolution.CanAddSolution(addedSolution) || addedSolution.Volume == 0) return false; - targetSolution.AddSolution(addedSolution); + targetSolution.AddSolution(addedSolution, _prototypeManager); UpdateChemicals(targetUid, targetSolution, true); return true; } @@ -245,13 +245,13 @@ public sealed partial class SolutionContainerSystem : EntitySystem if (quantity < 0) return TryTransferSolution(targetUid, sourceUid, target, source, -quantity); - quantity = FixedPoint2.Min(quantity, target.AvailableVolume, source.CurrentVolume); + quantity = FixedPoint2.Min(quantity, target.AvailableVolume, source.Volume); if (quantity == 0) return false; - // TODO after #12428 is merged, this should be made into a function that directly transfers reagents. - // currently this is quite inefficient. - target.AddSolution(source.SplitSolution(quantity)); + // TODO This should be made into a function that directly transfers reagents. currently this is quite + // inefficient. + target.AddSolution(source.SplitSolution(quantity), _prototypeManager); UpdateChemicals(sourceUid, source, false); UpdateChemicals(targetUid, target, true); @@ -294,16 +294,16 @@ public sealed partial class SolutionContainerSystem : EntitySystem FixedPoint2 overflowThreshold, [NotNullWhen(true)] out Solution? overflowingSolution) { - if (addedSolution.TotalVolume == 0 || overflowThreshold > targetSolution.MaxVolume) + if (addedSolution.Volume == 0 || overflowThreshold > targetSolution.MaxVolume) { overflowingSolution = null; return false; } - targetSolution.AddSolution(addedSolution); + targetSolution.AddSolution(addedSolution, _prototypeManager); UpdateChemicals(targetUid, targetSolution, true); overflowingSolution = targetSolution.SplitSolution(FixedPoint2.Max(FixedPoint2.Zero, - targetSolution.CurrentVolume - overflowThreshold)); + targetSolution.Volume - overflowThreshold)); return true; } @@ -320,14 +320,16 @@ public sealed partial class SolutionContainerSystem : EntitySystem 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 + /// true if the solution already existed /// solution - public Solution EnsureSolution(EntityUid uid, string name, + public Solution EnsureSolution(EntityUid uid, string name, out bool existed, SolutionContainerManagerComponent? solutionsMgr = null) { if (!Resolve(uid, ref solutionsMgr, false)) @@ -335,15 +337,76 @@ public sealed partial class SolutionContainerSystem : EntitySystem solutionsMgr = EntityManager.EnsureComponent(uid); } - if (!solutionsMgr.Solutions.ContainsKey(name)) + if (!solutionsMgr.Solutions.TryGetValue(name, out var existing)) { var newSolution = new Solution() { Name = name }; solutionsMgr.Solutions.Add(name, newSolution); + existed = false; + return newSolution; } - return solutionsMgr.Solutions[name]; + existed = true; + return existing; } + /// + /// 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) + => EnsureSolution(uid, name, out _, solutionsMgr); + + /// + /// 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 + /// Ensures that the solution's maximum volume is larger than this value./param> + /// solution components used in resolves + /// solution + public Solution EnsureSolution(EntityUid uid, string name, FixedPoint2 minVol, out bool existed, + SolutionContainerManagerComponent? solutionsMgr = null) + { + if (!Resolve(uid, ref solutionsMgr, false)) + { + solutionsMgr = EntityManager.EnsureComponent(uid); + } + + if (!solutionsMgr.Solutions.TryGetValue(name, out var existing)) + { + var newSolution = new Solution() { Name = name }; + solutionsMgr.Solutions.Add(name, newSolution); + existed = false; + newSolution.MaxVolume = minVol; + return newSolution; + } + + existed = true; + existing.MaxVolume = FixedPoint2.Max(existing.MaxVolume, minVol); + return existing; + } + + public Solution EnsureSolution(EntityUid uid, string name, + IEnumerable reagents, + bool setMaxVol = true, + SolutionContainerManagerComponent? solutionsMgr = null) + { + if (!Resolve(uid, ref solutionsMgr, false)) + solutionsMgr = EntityManager.EnsureComponent(uid); + + if (!solutionsMgr.Solutions.TryGetValue(name, out var existing)) + { + var newSolution = new Solution(reagents, setMaxVol); + solutionsMgr.Solutions.Add(name, newSolution); + return newSolution; + } + + existing.SetContents(reagents, setMaxVol); + return existing; + } /// /// Removes an amount from all reagents in a solution, adding it to a new solution. /// @@ -446,10 +509,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem /// 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; + var heatCap = solution.GetHeatCapacity(_prototypeManager); + solution.Temperature = heatCap == 0 ? 0 : thermalEnergy / heatCap; UpdateChemicals(owner, solution, true); } @@ -464,7 +525,8 @@ public sealed partial class SolutionContainerSystem : EntitySystem if (thermalEnergy == 0.0f) return; - solution.ThermalEnergy += thermalEnergy; + var heatCap = solution.GetHeatCapacity(_prototypeManager); + solution.Temperature += heatCap == 0 ? 0 : thermalEnergy / heatCap; UpdateChemicals(owner, solution, true); } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs index 588cfb84fc..d5a5825ce8 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnCollideSystem.cs @@ -52,7 +52,7 @@ namespace Content.Server.Chemistry.EntitySystems } var solRemoved = solution.SplitSolution(component.TransferAmount); - var solRemovedVol = solRemoved.TotalVolume; + var solRemovedVol = solRemoved.Volume; var solToInject = solRemoved.SplitSolution(solRemovedVol * component.TransferEfficiency); diff --git a/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs index 1db956e30a..3ed4a80af8 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionSpikableSystem.cs @@ -54,7 +54,7 @@ public sealed class SolutionSpikableSystem : EntitySystem return; } - if (targetSolution.CurrentVolume == 0 && !spikableSource.IgnoreEmpty) + if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty) { _popupSystem.PopupEntity(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user); return; @@ -66,7 +66,7 @@ public sealed class SolutionSpikableSystem : EntitySystem targetSolution.MaxVolume, out var overflow)) { - if (overflow.TotalVolume > 0) + if (overflow.Volume > 0) { RaiseLocalEvent(target, new SolutionSpikeOverflowEvent(overflow)); } diff --git a/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs index e454081c99..c9fbb9e762 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionTransferSystem.cs @@ -156,7 +156,7 @@ namespace Content.Server.Chemistry.EntitySystems return FixedPoint2.Zero; } - if (source.DrainAvailable == 0) + if (source.Volume == 0) { sourceEntity.PopupMessage(user, Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity))); @@ -178,7 +178,7 @@ namespace Content.Server.Chemistry.EntitySystems return FixedPoint2.Zero; } - var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.DrainAvailable, target.AvailableVolume)); + var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(source.Volume, target.AvailableVolume)); var solutionSystem = Get(); var solution = solutionSystem.Drain(sourceEntity, source, actualAmount); diff --git a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs index 6b1a64ca32..7e234e83bd 100644 --- a/Content.Server/Chemistry/EntitySystems/VaporSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/VaporSystem.cs @@ -71,7 +71,7 @@ namespace Content.Server.Chemistry.EntitySystems internal bool TryAddSolution(VaporComponent vapor, Solution solution) { - if (solution.TotalVolume == 0) + if (solution.Volume == 0) { return false; } @@ -120,7 +120,7 @@ namespace Content.Server.Chemistry.EntitySystems } } - if (contents.CurrentVolume == 0) + if (contents.Volume == 0) { // Delete this EntityManager.QueueDeleteEntity(entity); diff --git a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs index 4221168f52..6967a8fb82 100644 --- a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs +++ b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs @@ -77,7 +77,7 @@ namespace Content.Server.Chemistry.ReactionEffects if (args.Source == null) return; - var splitSolution = EntitySystem.Get().SplitSolution(args.SolutionEntity, args.Source, args.Source.MaxVolume); + var splitSolution = EntitySystem.Get().SplitSolution(args.SolutionEntity, args.Source, args.Source.Volume); // We take the square root so it becomes harder to reach higher amount values var amount = (int) Math.Round(_rangeConstant + _rangeMultiplier*Math.Sqrt(args.Quantity.Float())); amount = Math.Min(amount, _maxRange); @@ -90,7 +90,7 @@ namespace Content.Server.Chemistry.ReactionEffects // Weird formulas here but basically when amount increases, solutionFraction gets closer to 0 in a reciprocal manner // _reagentDilutionFactor defines how fast solutionFraction gets closer to 0 float solutionFraction = 1 / (_reagentDilutionFactor*(amount) + 1); - splitSolution.RemoveSolution(splitSolution.TotalVolume * (1 - solutionFraction)); + splitSolution.RemoveSolution(splitSolution.Volume * (1 - solutionFraction)); } var transform = args.EntityManager.GetComponent(args.SolutionEntity); diff --git a/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs b/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs index 5a77366688..4d30ec4643 100644 --- a/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs +++ b/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs @@ -1,5 +1,7 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Prototypes; +using static Robust.Shared.Physics.DynamicTree; namespace Content.Server.Chemistry.ReactionEffects { @@ -28,13 +30,12 @@ namespace Content.Server.Chemistry.ReactionEffects /// Adjusts the temperature of the solution involved in the reaction. /// [DataDefinition] - [Virtual] - public class AdjustSolutionTemperatureEffect : ReagentEffect + public sealed class AdjustSolutionTemperatureEffect : ReagentEffect { /// - /// The total change in the thermal energy of the solution. + /// The change in temperature. /// - [DataField("delta", required: true)] protected float Delta; + [DataField("delta", required: true)] private float _delta; /// /// The minimum temperature this effect can reach. @@ -51,47 +52,62 @@ namespace Content.Server.Chemistry.ReactionEffects /// [DataField("scaled")] private bool _scaled; - /// - /// - /// - /// - /// - protected virtual float GetDeltaT(Solution solution) => Delta; - public override void Effect(ReagentEffectArgs args) { var solution = args.Source; - if (solution == null) + if (solution == null || solution.Volume == 0) return; - var deltaT = GetDeltaT(solution); - if (_scaled) - deltaT = deltaT * (float) args.Quantity; - - if (deltaT == 0.0d) - return; - if (deltaT > 0.0d && solution.Temperature >= _maxTemp) - return; - if (deltaT < 0.0d && solution.Temperature <= _minTemp) - return; - - solution.Temperature = MathF.Max(MathF.Min(solution.Temperature + deltaT, _minTemp), _maxTemp); + var deltaT = _scaled ? _delta * (float) args.Quantity : _delta; + solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp); } } /// /// Adjusts the thermal energy of the solution involved in the reaction. /// - public sealed class AdjustSolutionThermalEnergyEffect : AdjustSolutionTemperatureEffect + public sealed class AdjustSolutionThermalEnergyEffect : ReagentEffect { - protected override float GetDeltaT(Solution solution) + /// + /// The change in energy. + /// + [DataField("delta", required: true)] private float _delta; + + /// + /// The minimum temperature this effect can reach. + /// + [DataField("minTemp")] private float _minTemp = 0.0f; + + /// + /// The maximum temperature this effect can reach. + /// + [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity; + + /// + /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount. + /// + [DataField("scaled")] private bool _scaled; + + public override void Effect(ReagentEffectArgs args) { - var heatCapacity = solution.HeatCapacity; - if (heatCapacity == 0.0f) - return 0.0f; - return Delta / heatCapacity; + var solution = args.Source; + if (solution == null || solution.Volume == 0) + return; + + if (_delta > 0 && solution.Temperature >= _maxTemp) + return; + if (_delta < 0 && solution.Temperature <= _minTemp) + return; + + var heatCap = solution.GetHeatCapacity(null); + var deltaT = _scaled + ? _delta / heatCap * (float) args.Quantity + : _delta / heatCap; + + solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp); } } + } diff --git a/Content.Server/Chemistry/ReagentEffectConditions/SolutionThermalEnergy.cs b/Content.Server/Chemistry/ReagentEffectConditions/SolutionThermalEnergy.cs deleted file mode 100644 index 2cca7b295f..0000000000 --- a/Content.Server/Chemistry/ReagentEffectConditions/SolutionThermalEnergy.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Chemistry.Reagent; - -namespace Content.Server.Chemistry.ReagentEffectConditions -{ - /// - /// Requires the solution to be above or below a certain thermal energy. - /// Used for things like explosives. - /// - public sealed class SolutionThermalEnergy : ReagentEffectCondition - { - [DataField("min")] - public float Min = 0.0f; - - [DataField("max")] - public float Max = float.PositiveInfinity; - public override bool Condition(ReagentEffectArgs args) - { - if (args.Source == null) - return false; - if (args.Source.ThermalEnergy < Min) - return false; - if (args.Source.ThermalEnergy > Max) - return false; - - return true; - } - } -} diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs index 4f94efe76f..d7145cf031 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SolutionExplosionBehavior.cs @@ -22,11 +22,11 @@ namespace Content.Server.Destructible.Thresholds.Behaviors && system.EntityManager.TryGetComponent(owner, out ExplosiveComponent? explosiveComponent)) { // Don't explode if there's no solution - if (explodingSolution.CurrentVolume == 0) + if (explodingSolution.Volume == 0) return; // Scale the explosion intensity based on the remaining volume of solution - var explosionScaleFactor = (explodingSolution.CurrentVolume.Float() / explodingSolution.MaxVolume.Float()); + var explosionScaleFactor = explodingSolution.FillFraction; // TODO: Perhaps some of the liquid should be discarded as if it's being consumed by the explosion diff --git a/Content.Server/Extinguisher/FireExtinguisherSystem.cs b/Content.Server/Extinguisher/FireExtinguisherSystem.cs index 4c5b7c140e..9f5935dcff 100644 --- a/Content.Server/Extinguisher/FireExtinguisherSystem.cs +++ b/Content.Server/Extinguisher/FireExtinguisherSystem.cs @@ -78,7 +78,7 @@ public sealed class FireExtinguisherSystem : EntitySystem { transfer = solTrans.TransferAmount; } - transfer = FixedPoint2.Min(transfer, targetSolution.DrainAvailable); + transfer = FixedPoint2.Min(transfer, targetSolution.Volume); if (transfer > 0) { diff --git a/Content.Server/Fluids/Components/PuddleComponent.cs b/Content.Server/Fluids/Components/PuddleComponent.cs index ae49b6c6a8..84b35af4d1 100644 --- a/Content.Server/Fluids/Components/PuddleComponent.cs +++ b/Content.Server/Fluids/Components/PuddleComponent.cs @@ -36,9 +36,6 @@ namespace Content.Server.Fluids.Components [DataField("spillSound")] public SoundSpecifier SpillSound = new SoundPathSpecifier("/Audio/Effects/Fluids/splat.ogg"); - [ViewVariables(VVAccess.ReadOnly)] - public FixedPoint2 CurrentVolume => EntitySystem.Get().CurrentVolume(Owner); - [DataField("overflowVolume")] public FixedPoint2 OverflowVolume = DefaultOverflowVolume; diff --git a/Content.Server/Fluids/EntitySystems/DrainSystem.cs b/Content.Server/Fluids/EntitySystems/DrainSystem.cs index a731fa1c8a..0e855c6c77 100644 --- a/Content.Server/Fluids/EntitySystems/DrainSystem.cs +++ b/Content.Server/Fluids/EntitySystems/DrainSystem.cs @@ -85,11 +85,11 @@ namespace Content.Server.Fluids.EntitySystems // the puddle's remaining volume (making it cleanly zero) // the drain's remaining volume in its buffer. var transferSolution = _solutionSystem.SplitSolution(puddle, puddleSolution, - FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.CurrentVolume, drainSolution.AvailableVolume)); + FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume)); _solutionSystem.TryAddSolution(drain.Owner, drainSolution, transferSolution); - if (puddleSolution.CurrentVolume <= 0) + if (puddleSolution.Volume <= 0) { QueueDel(puddle); } diff --git a/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs b/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs index ea47240da3..cba34173c1 100644 --- a/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs +++ b/Content.Server/Fluids/EntitySystems/EvaporationSystem.cs @@ -33,12 +33,12 @@ namespace Content.Server.Fluids.EntitySystems if (evaporationComponent.EvaporationToggle == true) { _solutionContainerSystem.SplitSolution(uid, solution, - FixedPoint2.Min(FixedPoint2.New(1), solution.CurrentVolume)); // removes 1 unit, or solution current volume, whichever is lower. + FixedPoint2.Min(FixedPoint2.New(1), solution.Volume)); // removes 1 unit, or solution current volume, whichever is lower. } evaporationComponent.EvaporationToggle = - solution.CurrentVolume > evaporationComponent.LowerLimit - && solution.CurrentVolume < evaporationComponent.UpperLimit; + solution.Volume > evaporationComponent.LowerLimit + && solution.Volume < evaporationComponent.UpperLimit; } } diff --git a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs index 776a88991c..b21315aab0 100644 --- a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs +++ b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs @@ -60,13 +60,13 @@ public sealed class MoppingSystem : EntitySystem /// private bool TryCreatePuddle(EntityUid user, EntityCoordinates clickLocation, AbsorbentComponent absorbent, Solution absorberSoln) { - if (absorberSoln.CurrentVolume <= 0) + if (absorberSoln.Volume <= 0) return false; if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid)) return false; - var releaseAmount = FixedPoint2.Min(absorbent.ResidueAmount, absorberSoln.CurrentVolume); + var releaseAmount = FixedPoint2.Min(absorbent.ResidueAmount, absorberSoln.Volume); var releasedSolution = _solutionSystem.SplitSolution(absorbent.Owner, absorberSoln, releaseAmount); _spillableSystem.SpillAt(mapGrid.GetTileRef(clickLocation), releasedSolution, PuddlePrototypeId); _popups.PopupEntity(Loc.GetString("mopping-system-release-to-floor"), user, user); @@ -84,7 +84,7 @@ public sealed class MoppingSystem : EntitySystem if (!_solutionSystem.TryGetDrainableSolution(target, out var drainableSolution)) return false; - if (drainableSolution.CurrentVolume <= 0) + if (drainableSolution.Volume <= 0) { var msg = Loc.GetString("mopping-system-target-container-empty", ("target", target)); _popups.PopupEntity(msg, user, user); @@ -93,7 +93,7 @@ public sealed class MoppingSystem : EntitySystem // Let's transfer up to to half the tool's available capacity to the tool. var quantity = FixedPoint2.Max(component.PickupAmount, absorberSoln.AvailableVolume / 2); - quantity = FixedPoint2.Min(quantity, drainableSolution.CurrentVolume); + quantity = FixedPoint2.Min(quantity, drainableSolution.Volume); DoMopInteraction(user, used, target, component, drainable.Solution, quantity, 1, "mopping-system-drainable-success", component.TransferSound); return true; @@ -104,7 +104,7 @@ public sealed class MoppingSystem : EntitySystem /// private bool TryEmptyAbsorber(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, Solution absorberSoln) { - if (absorberSoln.CurrentVolume <= 0 || !TryComp(target, out RefillableSolutionComponent? refillable)) + if (absorberSoln.Volume <= 0 || !TryComp(target, out RefillableSolutionComponent? refillable)) return false; if (!_solutionSystem.TryGetRefillableSolution(target, out var targetSolution)) @@ -128,7 +128,7 @@ public sealed class MoppingSystem : EntitySystem } float delay; - FixedPoint2 quantity = absorberSoln.CurrentVolume; + FixedPoint2 quantity = absorberSoln.Volume; // TODO this really needs cleaning up. Less magic numbers, more data-fields. @@ -171,7 +171,7 @@ public sealed class MoppingSystem : EntitySystem if (!TryComp(target, out PuddleComponent? puddle)) return false; - if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || puddleSolution.TotalVolume <= 0) + if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || puddleSolution.Volume <= 0) return false; FixedPoint2 quantity; @@ -186,12 +186,12 @@ public sealed class MoppingSystem : EntitySystem } // Can our absorber even absorb any liquid? - if (puddleSolution.TotalVolume <= lowerLimit) + if (puddleSolution.Volume <= lowerLimit) { // Cannot absorb any more liquid. So clearly the user wants to add liquid to the puddle... right? // This is the old behavior and I CBF fixing this, for the record I don't like this. - quantity = FixedPoint2.Min(absorber.ResidueAmount, absorberSoln.CurrentVolume); + quantity = FixedPoint2.Min(absorber.ResidueAmount, absorberSoln.Volume); if (quantity <= 0) return false; @@ -208,7 +208,7 @@ public sealed class MoppingSystem : EntitySystem return true; } - quantity = FixedPoint2.Min(absorber.PickupAmount, puddleSolution.TotalVolume - lowerLimit, absorberSoln.AvailableVolume); + quantity = FixedPoint2.Min(absorber.PickupAmount, puddleSolution.Volume - lowerLimit, absorberSoln.AvailableVolume); if (quantity <= 0) return false; diff --git a/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs b/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs index 6799623f8e..2db1789da6 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Fluids.Components; +using Content.Server.Fluids.Components; using Content.Shared.Fluids; using Robust.Server.Player; using Robust.Shared.Map; @@ -10,6 +10,7 @@ public sealed class PuddleDebugDebugOverlaySystem : SharedPuddleDebugOverlaySyst { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly PuddleSystem _puddle = default!; private readonly HashSet _playerObservers = new(); @@ -72,7 +73,8 @@ public sealed class PuddleDebugDebugOverlaySystem : SharedPuddleDebugOverlaySyst continue; var pos = xform.Coordinates.ToVector2i(EntityManager, _mapManager); - data.Add(new PuddleDebugOverlayData(pos, puddle.CurrentVolume)); + var vol = _puddle.CurrentVolume(uid, puddle); + data.Add(new PuddleDebugOverlayData(pos, vol)); } RaiseNetworkEvent(new PuddleOverlayDebugMessage(gridUid, data.ToArray())); diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index 6502c29043..7eb16ec0cb 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Player; using Solution = Content.Shared.Chemistry.Components.Solution; +using Robust.Shared.Prototypes; namespace Content.Server.Fluids.EntitySystems { @@ -19,6 +20,10 @@ namespace Content.Server.Fluids.EntitySystems [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly FluidSpreaderSystem _fluidSpreaderSystem = default!; [Dependency] private readonly StepTriggerSystem _stepTrigger = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + public static float PuddleVolume = 1000; // Using local deletion queue instead of the standard queue so that we can easily "undelete" if a puddle // loses & then gains reagents in a single tick. @@ -47,8 +52,7 @@ namespace Content.Server.Fluids.EntitySystems private void OnPuddleInit(EntityUid uid, PuddleComponent component, ComponentInit args) { - var solution = _solutionContainerSystem.EnsureSolution(uid, component.SolutionName); - solution.MaxVolume = FixedPoint2.New(1000); + _solutionContainerSystem.EnsureSolution(uid, component.SolutionName, FixedPoint2.New(PuddleVolume), out _); } private void OnSolutionUpdate(EntityUid uid, PuddleComponent component, SolutionChangedEvent args) @@ -56,7 +60,7 @@ namespace Content.Server.Fluids.EntitySystems if (args.Solution.Name != component.SolutionName) return; - if (args.Solution.CurrentVolume <= 0) + if (args.Solution.Volume <= 0) { _deletionQueue.Add(uid); return; @@ -77,10 +81,10 @@ namespace Content.Server.Fluids.EntitySystems // Opacity based on level of fullness to overflow // Hard-cap lower bound for visibility reasons - var volumeScale = CurrentVolume(puddleComponent.Owner, puddleComponent).Float() / + var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName); + var volumeScale = puddleSolution.Volume.Float() / puddleComponent.OverflowVolume.Float() * puddleComponent.OpacityModifier; - var puddleSolution = _solutionContainerSystem.EnsureSolution(uid, puddleComponent.SolutionName); bool isEvaporating; @@ -91,21 +95,24 @@ namespace Content.Server.Fluids.EntitySystems } else isEvaporating = false; - appearance.SetData(PuddleVisuals.VolumeScale, volumeScale); - appearance.SetData(PuddleVisuals.CurrentVolume, puddleComponent.CurrentVolume); - appearance.SetData(PuddleVisuals.SolutionColor, puddleSolution.Color); - appearance.SetData(PuddleVisuals.IsEvaporatingVisual, isEvaporating); + var color = puddleSolution.GetColor(_protoMan); + + _appearance.SetData(uid, PuddleVisuals.VolumeScale, volumeScale, appearance); + _appearance.SetData(uid, PuddleVisuals.CurrentVolume, puddleSolution.Volume, appearance); + _appearance.SetData(uid, PuddleVisuals.SolutionColor, color, appearance); + _appearance.SetData(uid, PuddleVisuals.IsEvaporatingVisual, isEvaporating, appearance); } private void UpdateSlip(EntityUid entityUid, PuddleComponent puddleComponent) { + var vol = CurrentVolume(puddleComponent.Owner, puddleComponent); if ((puddleComponent.SlipThreshold == FixedPoint2.New(-1) || - CurrentVolume(puddleComponent.Owner, puddleComponent) < puddleComponent.SlipThreshold) && + vol < puddleComponent.SlipThreshold) && TryComp(entityUid, out StepTriggerComponent? stepTrigger)) { _stepTrigger.SetActive(entityUid, false, stepTrigger); } - else if (CurrentVolume(puddleComponent.Owner, puddleComponent) >= puddleComponent.SlipThreshold) + else if (vol >= puddleComponent.SlipThreshold) { var comp = EnsureComp(entityUid); _stepTrigger.SetActive(entityUid, true, comp); @@ -143,7 +150,7 @@ namespace Content.Server.Fluids.EntitySystems return _solutionContainerSystem.TryGetSolution(puddleComponent.Owner, puddleComponent.SolutionName, out var solution) - ? solution.CurrentVolume + ? solution.Volume : FixedPoint2.Zero; } @@ -165,15 +172,16 @@ namespace Content.Server.Fluids.EntitySystems if (!Resolve(puddleUid, ref puddleComponent)) return false; - if (addedSolution.TotalVolume == 0 || + if (addedSolution.Volume == 0 || !_solutionContainerSystem.TryGetSolution(puddleComponent.Owner, puddleComponent.SolutionName, out var solution)) { return false; } - solution.AddSolution(addedSolution); + solution.AddSolution(addedSolution, _protoMan); _solutionContainerSystem.UpdateChemicals(puddleUid, solution, true); + if (checkForOverflow && IsOverflowing(puddleUid, puddleComponent)) { _fluidSpreaderSystem.AddOverflowingPuddle(puddleComponent.Owner, puddleComponent); @@ -215,7 +223,7 @@ namespace Content.Server.Fluids.EntitySystems out var destSolution)) continue; - var takeAmount = FixedPoint2.Max(0, dividedVolume - destSolution.CurrentVolume); + var takeAmount = FixedPoint2.Max(0, dividedVolume - destSolution.Volume); TryAddSolution(destPuddle.Owner, srcSolution.SplitSolution(takeAmount), false, false, destPuddle); if (stillOverflowing != null && IsOverflowing(destPuddle.Owner, destPuddle)) { @@ -223,7 +231,7 @@ namespace Content.Server.Fluids.EntitySystems } } - if (stillOverflowing != null && srcSolution.CurrentVolume > sourcePuddleComponent.OverflowVolume) + if (stillOverflowing != null && srcSolution.Volume > sourcePuddleComponent.OverflowVolume) { stillOverflowing.Add(srcPuddle); } @@ -241,7 +249,7 @@ namespace Content.Server.Fluids.EntitySystems if (!Resolve(uid, ref puddle)) return false; - return CurrentVolume(uid, puddle) + solution.TotalVolume > puddle.OverflowVolume; + return CurrentVolume(uid, puddle) + solution.Volume > puddle.OverflowVolume; } /// diff --git a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs index 963e0dd8e0..00a4af77a9 100644 --- a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs @@ -67,11 +67,11 @@ public sealed class SpillableSystem : EntitySystem if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out var solution)) return; - if (solution.TotalVolume == 0) + if (solution.Volume == 0) return; // spill all solution on the player - var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable); + var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.Volume); SpillAt(args.Equipee, drainedSolution, "PuddleSmear"); } @@ -108,7 +108,7 @@ public sealed class SpillableSystem : EntitySystem $"{ToPrettyString(uid):entity} spilled a solution {SolutionContainerSystem.ToPrettyString(solution):solution} on landing"); } - var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.DrainAvailable); + var drainedSolution = _solutionContainerSystem.Drain(uid, solution, solution.Volume); SpillAt(drainedSolution, EntityManager.GetComponent(uid).Coordinates, "PuddleSmear"); } @@ -123,7 +123,7 @@ public sealed class SpillableSystem : EntitySystem if (TryComp(args.Target, out var drink) && (!drink.Opened)) return; - if (solution.DrainAvailable == FixedPoint2.Zero) + if (solution.Volume == FixedPoint2.Zero) return; Verb verb = new(); @@ -134,7 +134,7 @@ public sealed class SpillableSystem : EntitySystem verb.Act = () => { var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target, - solution, solution.DrainAvailable); + solution, solution.Volume); SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear"); }; } @@ -176,7 +176,7 @@ public sealed class SpillableSystem : EntitySystem public PuddleComponent? SpillAt(Solution solution, EntityCoordinates coordinates, string prototype, bool overflow = true, bool sound = true, bool combine = true) { - if (solution.TotalVolume == 0) return null; + if (solution.Volume == 0) return null; if (!_mapManager.TryGetGrid(coordinates.GetGridUid(EntityManager), out var mapGrid)) @@ -204,7 +204,7 @@ public sealed class SpillableSystem : EntitySystem public PuddleComponent? SpillAt(TileRef tileRef, Solution solution, string prototype, bool overflow = true, bool sound = true, bool noTileReact = false, bool combine = true) { - if (solution.TotalVolume <= 0) return null; + if (solution.Volume <= 0) return null; // If space return early, let that spill go out into the void if (tileRef.Tile.IsEmpty) return null; @@ -226,7 +226,7 @@ public sealed class SpillableSystem : EntitySystem } // Tile reactions used up everything. - if (solution.CurrentVolume == FixedPoint2.Zero) + if (solution.Volume == FixedPoint2.Zero) return null; // Get normalized co-ordinate for spill location and spill it in the centre @@ -268,11 +268,11 @@ public sealed class SpillableSystem : EntitySystem component.CancelToken = null; //solution gone by other means before doafter completes - if (ev.Solution == null || ev.Solution.CurrentVolume == 0) + if (ev.Solution == null || ev.Solution.Volume == 0) return; var puddleSolution = _solutionContainerSystem.SplitSolution(uid, - ev.Solution, ev.Solution.DrainAvailable); + ev.Solution, ev.Solution.Volume); SpillAt(puddleSolution, Transform(component.Owner).Coordinates, "PuddleSmear"); } diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs index f7897a59f9..372eaeec37 100644 --- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Vapor; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Server.Fluids.EntitySystems; @@ -19,6 +20,7 @@ namespace Content.Server.Fluids.EntitySystems; public sealed class SpraySystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; @@ -51,7 +53,7 @@ public sealed class SpraySystem : EntitySystem && curTime < cooldown.CooldownEnd) return; - if (solution.CurrentVolume <= 0) + if (solution.Volume <= 0) { _popupSystem.PopupEntity(Loc.GetString("spray-component-is-empty-message"), uid, args.User); @@ -76,7 +78,7 @@ public sealed class SpraySystem : EntitySystem var threeQuarters = diffNorm * 0.75f; var quarter = diffNorm * 0.25f; - var amount = Math.Max(Math.Min((solution.CurrentVolume / component.TransferAmount).Int(), component.VaporAmount), 1); + var amount = Math.Max(Math.Min((solution.Volume / component.TransferAmount).Int(), component.VaporAmount), 1); var spread = component.VaporSpread / amount; for (var i = 0; i < amount; i++) @@ -94,7 +96,7 @@ public sealed class SpraySystem : EntitySystem var newSolution = _solutionContainerSystem.SplitSolution(uid, solution, component.TransferAmount); - if (newSolution.TotalVolume <= FixedPoint2.Zero) + if (newSolution.Volume <= FixedPoint2.Zero) break; // Spawn the vapor cloud onto the grid/map the user is present on. Offset the start position based on how far the target destination is. @@ -106,7 +108,7 @@ public sealed class SpraySystem : EntitySystem if (TryComp(vapor, out AppearanceComponent? appearance)) { - appearance.SetData(VaporVisuals.Color, solution.Color.WithAlpha(1f)); + appearance.SetData(VaporVisuals.Color, solution.GetColor(_proto).WithAlpha(1f)); appearance.SetData(VaporVisuals.State, true); } diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 7ae53671a2..4198e6d703 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -87,7 +87,7 @@ namespace Content.Server.Kitchen.EntitySystems if (TryComp(item, out var stack)) { - var totalVolume = solution.TotalVolume * stack.Count; + var totalVolume = solution.Volume * stack.Count; if (totalVolume <= 0) continue; @@ -102,7 +102,7 @@ namespace Content.Server.Kitchen.EntitySystems } else { - if (solution.TotalVolume > containerSolution.AvailableVolume) + if (solution.Volume > containerSolution.AvailableVolume) continue; QueueDel(item); diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index 67d7e34222..694ff77b59 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; @@ -204,7 +204,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution)) { args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value)))); - if (containerSolution.CurrentVolume == 0) + if (containerSolution.Volume == 0) { args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker")); } diff --git a/Content.Server/Nutrition/Components/FoodComponent.cs b/Content.Server/Nutrition/Components/FoodComponent.cs index ca7a084758..511a298867 100644 --- a/Content.Server/Nutrition/Components/FoodComponent.cs +++ b/Content.Server/Nutrition/Components/FoodComponent.cs @@ -71,11 +71,11 @@ namespace Content.Server.Nutrition.Components } if (TransferAmount == null) - return solution.CurrentVolume == 0 ? 0 : 1; + return solution.Volume == 0 ? 0 : 1; - return solution.CurrentVolume == 0 + return solution.Volume == 0 ? 0 - : Math.Max(1, (int) Math.Ceiling((solution.CurrentVolume / (FixedPoint2)TransferAmount).Float())); + : Math.Max(1, (int) Math.Ceiling((solution.Volume / (FixedPoint2)TransferAmount).Float())); } } } diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 062b1d7918..49883f93bd 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -164,7 +164,7 @@ namespace Content.Server.Nutrition.EntitySystems component.Opened = true; UpdateAppearance(component); - var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.DrainAvailable); + var solution = _solutionContainerSystem.Drain(uid, interactions, interactions.Volume); _spillableSystem.SpillAt(uid, solution, "PuddleSmear"); _audio.PlayPvs(_audio.GetSound(component.BurstSound), uid, AudioParams.Default.WithVolume(-4)); @@ -240,7 +240,7 @@ namespace Content.Server.Nutrition.EntitySystems } if (!_solutionContainerSystem.TryGetDrainableSolution(drink.Owner, out var drinkSolution) || - drinkSolution.DrainAvailable <= 0) + drinkSolution.Volume <= 0) { _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty", ("entity", EntityManager.GetComponent(drink.Owner).EntityName)), drink.Owner, user); @@ -301,7 +301,7 @@ namespace Content.Server.Nutrition.EntitySystems return; args.Drink.CancelToken = null; - var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.DrainAvailable); + var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.Volume); var drained = _solutionContainerSystem.Drain(args.Drink.Owner, args.DrinkSolution, transferAmount); var forceDrink = uid != args.User; diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index dc4938ff1e..76488ab09a 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -163,8 +163,8 @@ namespace Content.Server.Nutrition.EntitySystems return; var transferAmount = args.Food.TransferAmount != null - ? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.CurrentVolume) - : args.FoodSolution.CurrentVolume; + ? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.Volume) + : args.FoodSolution.Volume; var split = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount); diff --git a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs index 3cd84ecb28..ef2e84ca6a 100644 --- a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs @@ -59,7 +59,7 @@ namespace Content.Server.Nutrition.EntitySystems var sliceUid = EntityManager.SpawnEntity(component.Slice, transform.Coordinates); var lostSolution = _solutionContainerSystem.SplitSolution(uid, solution, - solution.CurrentVolume / FixedPoint2.New(component.Count)); + solution.Volume / FixedPoint2.New(component.Count)); // Fill new slice FillSlice(sliceUid, lostSolution); diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs index 9f06a96e70..b5bcf59ec6 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs @@ -117,12 +117,12 @@ namespace Content.Server.Nutrition.EntitySystems var inhaledSolution = _solutionContainerSystem.SplitSolution(uid, solution, smokable.InhaleAmount * _timer); - if (solution.TotalVolume == FixedPoint2.Zero) + if (solution.Volume == FixedPoint2.Zero) { RaiseLocalEvent(uid, new SmokableSolutionEmptyEvent(), true); } - if (inhaledSolution.TotalVolume == FixedPoint2.Zero) + if (inhaledSolution.Volume == FixedPoint2.Zero) continue; // This is awful. I hate this so much. diff --git a/Content.Server/Nutrition/EntitySystems/TrashOnEmptySystem.cs b/Content.Server/Nutrition/EntitySystems/TrashOnEmptySystem.cs index c0d57244a1..67f95b2764 100644 --- a/Content.Server/Nutrition/EntitySystems/TrashOnEmptySystem.cs +++ b/Content.Server/Nutrition/EntitySystems/TrashOnEmptySystem.cs @@ -39,7 +39,7 @@ namespace Content.Server.Nutrition.EntitySystems public void UpdateTags(TrashOnEmptyComponent component, Solution solution) { - if (solution.DrainAvailable <= 0) + if (solution.Volume <= 0) { _tagSystem.AddTag(component.Owner, "Trash"); return; diff --git a/Content.Server/Payload/EntitySystems/PayloadSystem.cs b/Content.Server/Payload/EntitySystems/PayloadSystem.cs index 567edb7711..2bf78b2e93 100644 --- a/Content.Server/Payload/EntitySystems/PayloadSystem.cs +++ b/Content.Server/Payload/EntitySystems/PayloadSystem.cs @@ -147,8 +147,8 @@ public sealed class PayloadSystem : EntitySystem || !TryComp(beakerB, out FitsInDispenserComponent? compB) || !_solutionSystem.TryGetSolution(beakerA, compA.Solution, out var solutionA) || !_solutionSystem.TryGetSolution(beakerB, compB.Solution, out var solutionB) - || solutionA.TotalVolume == 0 - || solutionB.TotalVolume == 0) + || solutionA.Volume == 0 + || solutionB.Volume == 0) { return; } @@ -164,7 +164,7 @@ public sealed class PayloadSystem : EntitySystem _solutionSystem.RemoveAllSolution(beakerB, solutionB); // The grenade might be a dud. Redistribute solution: - var tmpSol = _solutionSystem.SplitSolution(beakerA, solutionA, solutionA.CurrentVolume * solutionB.MaxVolume / solutionA.MaxVolume); + var tmpSol = _solutionSystem.SplitSolution(beakerA, solutionA, solutionA.Volume * solutionB.MaxVolume / solutionA.MaxVolume); _solutionSystem.TryAddSolution(beakerB, solutionB, tmpSol); solutionA.MaxVolume -= solutionB.MaxVolume; _solutionSystem.UpdateChemicals(beakerA, solutionA, false); diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index e291869cf7..38f6e96146 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -120,7 +120,7 @@ public sealed class PowerCellSystem : SharedPowerCellSystem private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args) { component.IsRigged = _solutionsSystem.TryGetSolution(uid, PowerCellComponent.SolutionName, out var solution) - && solution.ContainsReagent("Plasma", out var plasma) + && solution.TryGetReagent("Plasma", out var plasma) && plasma >= 5; if (component.IsRigged) diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs index 29ed1fc296..8166164a85 100644 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ b/Content.Server/Tools/ToolSystem.Welder.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; @@ -221,7 +221,7 @@ namespace Content.Server.Tools && _solutionContainerSystem.TryGetDrainableSolution(target, out var targetSolution) && _solutionContainerSystem.TryGetSolution(uid, welder.FuelSolution, out var welderSolution)) { - var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.DrainAvailable); + var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume); if (trans > 0) { var drained = _solutionContainerSystem.Drain(target, targetSolution, trans); diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index aa86696bf5..4f7017c77d 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -250,9 +250,9 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem return; var removedSolution = solutionContainer.SplitSolution(comp.TransferAmount * hitBloodstreams.Count); - var removedVol = removedSolution.TotalVolume; + var removedVol = removedSolution.Volume; var solutionToInject = removedSolution.SplitSolution(removedVol * comp.TransferEfficiency); - var volPerBloodstream = solutionToInject.TotalVolume * (1 / hitBloodstreams.Count); + var volPerBloodstream = solutionToInject.Volume * (1 / hitBloodstreams.Count); foreach (var bloodstream in hitBloodstreams) { diff --git a/Content.Server/Weapons/Ranged/Systems/ChemicalAmmoSystem.cs b/Content.Server/Weapons/Ranged/Systems/ChemicalAmmoSystem.cs index 325d60e289..c6bc89ffe4 100644 --- a/Content.Server/Weapons/Ranged/Systems/ChemicalAmmoSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/ChemicalAmmoSystem.cs @@ -35,7 +35,7 @@ namespace Content.Server.Weapons.Ranged.Systems if (!projectileSolutionContainers.Any()) return; - var solutionPerProjectile = ammoSolution.CurrentVolume * (1 / projectileSolutionContainers.Count); + var solutionPerProjectile = ammoSolution.Volume * (1 / projectileSolutionContainers.Count); foreach (var (projectileUid, projectileSolution) in projectileSolutionContainers) { diff --git a/Content.Shared/Chemistry/Components/Solution.Managerial.cs b/Content.Shared/Chemistry/Components/Solution.Managerial.cs deleted file mode 100644 index 436d7e186a..0000000000 --- a/Content.Shared/Chemistry/Components/Solution.Managerial.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Chemistry.Components -{ - public sealed partial class Solution - { - - /// - /// If reactions will be checked for when adding reagents to the container. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canReact")] - public bool CanReact { get; set; } = true; - - /// - /// If reactions can occur via mixing. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canMix")] - public bool CanMix { get; set; } = false; - - /// - /// Volume needed to fill this container. - /// - [ViewVariables] - public FixedPoint2 AvailableVolume => MaxVolume - CurrentVolume; - - public FixedPoint2 DrawAvailable => CurrentVolume; - public FixedPoint2 DrainAvailable => CurrentVolume; - - /// - /// Checks if a solution can fit into the container. - /// - /// The solution that is trying to be added. - /// If the solution can be fully added. - public bool CanAddSolution(Solution solution) - { - return solution.TotalVolume <= AvailableVolume; - } - - [DataField("maxSpillRefill")] - public FixedPoint2 MaxSpillRefill { get; set; } - - /// - /// Initially set . If empty will be calculated based - /// on sum of fixed units. - /// - [DataField("maxVol")] public FixedPoint2 InitialMaxVolume; - - [ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero; - - [ViewVariables] - public FixedPoint2 CurrentVolume => TotalVolume; - - /// - /// The total heat capacity of all reagents in the solution. - /// - [ViewVariables] - public float HeatCapacity => GetHeatCapacity(); - - /// - /// The average specific heat of all reagents in the solution. - /// - [ViewVariables] - public float SpecificHeat => HeatCapacity / (float) TotalVolume; - - /// - /// The total thermal energy of the reagents in the solution. - /// - [ViewVariables(VVAccess.ReadWrite)] - public float ThermalEnergy { - get { return Temperature * HeatCapacity; } - set { Temperature = ((HeatCapacity == 0.0f) ? 0.0f : (value / HeatCapacity)); } - } - - /// - /// Returns the total heat capacity of the reagents in this solution. - /// - /// The total heat capacity of the reagents in this solution. - private float GetHeatCapacity() - { - var heatCapacity = 0.0f; - var prototypeManager = IoCManager.Resolve(); - foreach(var reagent in Contents) - { - if (!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) - proto = new ReagentPrototype(); - - heatCapacity += (float) reagent.Quantity * proto.SpecificHeat; - } - - return heatCapacity; - } - } -} diff --git a/Content.Shared/Chemistry/Components/Solution.cs b/Content.Shared/Chemistry/Components/Solution.cs index a0849febda..c7b47cdea6 100644 --- a/Content.Shared/Chemistry/Components/Solution.cs +++ b/Content.Shared/Chemistry/Components/Solution.cs @@ -1,12 +1,13 @@ -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; +using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Utility; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Shared.Chemistry.Components { @@ -17,7 +18,8 @@ namespace Content.Shared.Chemistry.Components [DataDefinition] public sealed partial class Solution : IEnumerable, ISerializationHooks { - // Most objects on the station hold only 1 or 2 reagents + // This is a list because it is actually faster to add and remove reagents from + // a list than a dictionary, though contains-reagent checks are slightly slower, [DataField("reagents")] public List Contents = new(2); @@ -25,7 +27,41 @@ namespace Content.Shared.Chemistry.Components /// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker). /// [ViewVariables] - public FixedPoint2 TotalVolume { get; set; } + public FixedPoint2 Volume { get; set; } + + /// + /// Maximum volume this solution supports. + /// + /// + /// A value of zero means the maximum will automatically be set equal to the current volume during + /// initialization. Note that most solution methods ignore max volume altogether, but various solution + /// systems use this. + /// + [DataField("maxVol")] + [ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero; + + public float FillFraction => MaxVolume == 0 ? 1 : Volume.Float() / MaxVolume.Float(); + + /// + /// If reactions will be checked for when adding reagents to the container. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("canReact")] + public bool CanReact { get; set; } = true; + + /// + /// If reactions can occur via mixing. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("canMix")] + public bool CanMix { get; set; } = false; + + /// + /// Volume needed to fill this container. + /// + [ViewVariables] + public FixedPoint2 AvailableVolume => MaxVolume - Volume; /// /// The temperature of the reagents in the solution. @@ -34,40 +70,155 @@ namespace Content.Shared.Chemistry.Components [DataField("temperature")] public float Temperature { get; set; } = 293.15f; - public Color Color => GetColor(); - /// /// The name of this solution, if it is contained in some /// public string? Name; + /// + /// Checks if a solution can fit into the container. + /// + public bool CanAddSolution(Solution solution) + { + return solution.Volume <= AvailableVolume; + } + + /// + /// The total heat capacity of all reagents in the solution. + /// + [ViewVariables] + private float _heatCapacity; + + /// + /// If true, then needs to be recomputed. + /// + [ViewVariables] + private bool _heatCapacityDirty = true; + + public void UpdateHeatCapacity(IPrototypeManager? protoMan) + { + IoCManager.Resolve(ref protoMan); + DebugTools.Assert(_heatCapacityDirty); + _heatCapacityDirty = false; + _heatCapacity = 0; + foreach (var reagent in Contents) + { + _heatCapacity += (float) reagent.Quantity * protoMan.Index(reagent.ReagentId).SpecificHeat; + } + } + + public float GetHeatCapacity(IPrototypeManager? protoMan) + { + if (_heatCapacityDirty) + UpdateHeatCapacity(protoMan); + return _heatCapacity; + } + + /// /// Constructs an empty solution (ex. an empty beaker). /// - public Solution() { } + public Solution() : this(2) // Most objects on the station hold only 1 or 2 reagents. + { + } + + /// + /// Constructs an empty solution (ex. an empty beaker). + /// + public Solution(int capacity) + { + Contents = new(capacity); + } /// /// Constructs a solution containing 100% of a reagent (ex. A beaker of pure water). /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. - public Solution(string reagentId, FixedPoint2 quantity) + public Solution(string reagentId, FixedPoint2 quantity) : this() { AddReagent(reagentId, quantity); } + public Solution(IEnumerable reagents, bool setMaxVol = true) + { + Contents = new(reagents); + Volume = FixedPoint2.Zero; + foreach (var reagent in Contents) + { + Volume += reagent.Quantity; + } + + if (setMaxVol) + MaxVolume = Volume; + + ValidateSolution(); + } + + public Solution(Solution solution) + { + Volume = solution.Volume; + _heatCapacity = solution._heatCapacity; + _heatCapacityDirty = solution._heatCapacityDirty; + Contents = solution.Contents.ShallowClone(); + ValidateSolution(); + } + + public Solution Clone() + { + return new Solution(this); + } + + + [AssertionMethod] + public void ValidateSolution() + { + // sandbox forbids: [Conditional("DEBUG")] +#if DEBUG + // Correct volume + DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume); + + // All reagents have at least some reagent present. + DebugTools.Assert(!Contents.Any(x => x.Quantity <= FixedPoint2.Zero)); + + // No duplicate reagent iDs + DebugTools.Assert(Contents.Select(x => x.ReagentId).ToHashSet().Count() == Contents.Count); + + // If it isn't flagged as dirty, check heat capacity is correct. + if (!_heatCapacityDirty) + { + var cur = _heatCapacity; + _heatCapacityDirty = true; + UpdateHeatCapacity(null); + DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur)); + } +#endif + } + void ISerializationHooks.AfterDeserialization() { - TotalVolume = FixedPoint2.Zero; - Contents.ForEach(reagent => TotalVolume += reagent.Quantity); + Volume = FixedPoint2.Zero; + foreach (var reagent in Contents) + { + Volume += reagent.Quantity; + } + + if (MaxVolume == FixedPoint2.Zero) + MaxVolume = Volume; } public bool ContainsReagent(string reagentId) { - return ContainsReagent(reagentId, out _); + foreach (var reagent in Contents) + { + if (reagent.ReagentId == reagentId) + return true; + } + + return false; } - public bool ContainsReagent(string reagentId, out FixedPoint2 quantity) + public bool TryGetReagent(string reagentId, out FixedPoint2 quantity) { foreach (var reagent in Contents) { @@ -82,15 +233,22 @@ namespace Content.Shared.Chemistry.Components return false; } - public string GetPrimaryReagentId() + public string? GetPrimaryReagentId() { if (Contents.Count == 0) + return null; + + ReagentQuantity max = default; + + foreach (var reagent in Contents) { - return ""; + if (reagent.Quantity >= max.Quantity) + { + max = reagent; + } } - var majorReagent = Contents.MaxBy(reagent => reagent.Quantity); - return majorReagent.ReagentId; + return max.ReagentId!; } /// @@ -98,16 +256,16 @@ namespace Content.Shared.Chemistry.Components /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. - public void AddReagent(string reagentId, FixedPoint2 quantity, float? temperature = null) + public void AddReagent(string reagentId, FixedPoint2 quantity, bool dirtyHeatCap = true) { if (quantity <= 0) + { + DebugTools.Assert(quantity == 0, "Attempted to add negative reagent quantity"); return; - if (!IoCManager.Resolve().TryIndex(reagentId, out ReagentPrototype? proto)) - proto = new ReagentPrototype(); + } - var actualTemp = temperature ?? Temperature; - var oldThermalEnergy = Temperature * GetHeatCapacity(); - var addedThermalEnergy = (float) quantity * proto.SpecificHeat * actualTemp; + Volume += quantity; + _heatCapacityDirty |= dirtyHeatCap; for (var i = 0; i < Contents.Count; i++) { var reagent = Contents[i]; @@ -115,16 +273,65 @@ namespace Content.Shared.Chemistry.Components continue; Contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity); - - TotalVolume += quantity; - ThermalEnergy = oldThermalEnergy + addedThermalEnergy; + ValidateSolution(); return; } Contents.Add(new ReagentQuantity(reagentId, quantity)); + ValidateSolution(); + } - TotalVolume += quantity; - ThermalEnergy = oldThermalEnergy + addedThermalEnergy; + /// + /// Adds a given quantity of a reagent directly into the solution. + /// + /// The prototype of the reagent to add. + /// The quantity in milli-units. + public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity) + { + AddReagent(proto.ID, quantity, false); + _heatCapacity += quantity.Float() * proto.SpecificHeat; + } + + /// + /// Adds a given quantity of a reagent directly into the solution. + /// + /// The prototype of the reagent to add. + /// The quantity in milli-units. + public void AddReagent(ReagentPrototype proto, FixedPoint2 quantity, float temperature, IPrototypeManager? protoMan) + { + if (_heatCapacityDirty) + UpdateHeatCapacity(protoMan); + + var totalThermalEnergy = Temperature * _heatCapacity + temperature * proto.SpecificHeat; + AddReagent(proto, quantity); + Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity; + } + + + /// + /// Scales the amount of solution by some integer quantity. + /// + /// The scalar to modify the solution by. + public void ScaleSolution(int scale) + { + if (scale == 1) + return; + + if (scale == 0) + { + RemoveAllSolution(); + return; + } + + _heatCapacity *= scale; + Volume *= scale; + + for (int i = 0; i <= Contents.Count; i++) + { + var old = Contents[i]; + Contents[i] = new ReagentQuantity(old.ReagentId, old.Quantity * scale); + } + ValidateSolution(); } /// @@ -133,21 +340,31 @@ namespace Content.Shared.Chemistry.Components /// The scalar to modify the solution by. public void ScaleSolution(float scale) { - if (scale.Equals(1f)) + if (scale == 1) return; - var tempContents = new List(Contents); - foreach(var current in tempContents) + if (scale == 0) { - if(scale > 1) - { - AddReagent(current.ReagentId, current.Quantity * scale - current.Quantity); - } + RemoveAllSolution(); + return; + } + + Volume = FixedPoint2.Zero; + for (int i = Contents.Count - 1; i >= 0; i--) + { + var old = Contents[i]; + var newQuantity = old.Quantity * scale; + if (newQuantity == FixedPoint2.Zero) + Contents.RemoveSwap(i); else { - RemoveReagent(current.ReagentId, current.Quantity - current.Quantity * scale); + Contents[i] = new ReagentQuantity(old.ReagentId, newQuantity); + Volume += newQuantity; } } + + _heatCapacityDirty = true; + ValidateSolution(); } /// @@ -159,11 +376,12 @@ namespace Content.Shared.Chemistry.Components { for (var i = 0; i < Contents.Count; i++) { - if (Contents[i].ReagentId == reagentId) - return Contents[i].Quantity; + var reagent = Contents[i]; + if (reagent.ReagentId == reagentId) + return reagent.Quantity; } - return FixedPoint2.New(0); + return FixedPoint2.Zero; } /// @@ -174,7 +392,7 @@ namespace Content.Shared.Chemistry.Components /// How much reagent was actually removed. Zero if the reagent is not present on the solution. public FixedPoint2 RemoveReagent(string reagentId, FixedPoint2 quantity) { - if(quantity <= 0) + if (quantity <= FixedPoint2.Zero) return FixedPoint2.Zero; for (var i = 0; i < Contents.Count; i++) @@ -186,16 +404,19 @@ namespace Content.Shared.Chemistry.Components var curQuantity = reagent.Quantity; var newQuantity = curQuantity - quantity; + _heatCapacityDirty = true; if (newQuantity <= 0) { Contents.RemoveSwap(i); - TotalVolume -= curQuantity; + Volume -= curQuantity; + ValidateSolution(); return curQuantity; } Contents[i] = new ReagentQuantity(reagentId, newQuantity); - TotalVolume -= quantity; + Volume -= quantity; + ValidateSolution(); return quantity; } @@ -203,104 +424,150 @@ namespace Content.Shared.Chemistry.Components return FixedPoint2.Zero; } - /// - /// Remove the specified quantity from this solution. - /// - /// The quantity of this solution to remove - public void RemoveSolution(FixedPoint2 quantity) - { - if(quantity <= 0) - return; - - var ratio = (TotalVolume - quantity).Double() / TotalVolume.Double(); - - if (ratio <= 0) - { - RemoveAllSolution(); - return; - } - - for (var i = 0; i < Contents.Count; i++) - { - var reagent = Contents[i]; - var oldQuantity = reagent.Quantity; - - // quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest - // whole unit. This should prevent little bits of chemical remaining because of float rounding errors. - var newQuantity = oldQuantity * ratio; - - Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); - } - - TotalVolume *= ratio; - } - public void RemoveAllSolution() { Contents.Clear(); - TotalVolume = FixedPoint2.New(0); + Volume = FixedPoint2.Zero; + _heatCapacityDirty = false; + _heatCapacity = 0; } - public Solution SplitSolution(FixedPoint2 quantity) + public Solution SplitSolution(FixedPoint2 toTake) { - if (quantity <= 0) + if (toTake <= FixedPoint2.Zero) return new Solution(); Solution newSolution; - if (quantity >= TotalVolume) + if (toTake >= Volume) { newSolution = Clone(); RemoveAllSolution(); return newSolution; } - newSolution = new Solution(); - var newTotalVolume = FixedPoint2.New(0); - var newHeatCapacity = 0.0d; - var remainingVolume = TotalVolume; - var prototypeManager = IoCManager.Resolve(); + var origVol = Volume; + var effVol = Volume.Value; + newSolution = new Solution(Contents.Count) { Temperature = Temperature }; + var remaining = (long) toTake.Value; - for (var i = Contents.Count - 1; i >= 0; i--) + for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap. { - if (remainingVolume == FixedPoint2.Zero) - // shouldn't happen, but it can if someone, somehow has a reagent with 0-quantity in a solution. - break; - var reagent = Contents[i]; - var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double(); - if(!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) - proto = new ReagentPrototype(); - remainingVolume -= reagent.Quantity; + // This is set up such that integer rounding will tend to take more reagents. + var split = remaining * reagent.Quantity.Value / effVol; - var newQuantity = reagent.Quantity * ratio; - var splitQuantity = reagent.Quantity - newQuantity; + if (split <= 0) + { + effVol -= reagent.Quantity.Value; + DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?"); + continue; + } - if (newQuantity > 0) + var splitQuantity = FixedPoint2.FromCents((int) split); + var newQuantity = reagent.Quantity - splitQuantity; + + DebugTools.Assert(newQuantity >= 0); + + if (newQuantity > FixedPoint2.Zero) Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); else - Contents.RemoveAt(i); + Contents.RemoveSwap(i); - if (splitQuantity > 0) - newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity)); - - newTotalVolume += splitQuantity; - newHeatCapacity += (float) splitQuantity * proto.SpecificHeat; - quantity -= splitQuantity; + newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity)); + Volume -= splitQuantity; + remaining -= split; + effVol -= reagent.Quantity.Value; } - newSolution.TotalVolume = newTotalVolume; - newSolution.Temperature = Temperature; - TotalVolume -= newTotalVolume; + newSolution.Volume = origVol - Volume; + + DebugTools.Assert(remaining >= 0); + DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero); + + _heatCapacityDirty = true; + newSolution._heatCapacityDirty = true; + + ValidateSolution(); + newSolution.ValidateSolution(); return newSolution; } - public void AddSolution(Solution otherSolution) + /// + /// Variant of that doesn't return a new solution containing the removed reagents. + /// + /// The quantity of this solution to remove + public void RemoveSolution(FixedPoint2 toTake) { - var oldThermalEnergy = Temperature * GetHeatCapacity(); - var addedThermalEnergy = otherSolution.Temperature * otherSolution.GetHeatCapacity(); + if (toTake <= FixedPoint2.Zero) + return; + + if (toTake >= Volume) + { + RemoveAllSolution(); + return; + } + + var effVol = Volume.Value; + Volume -= toTake; + var remaining = (long) toTake.Value; + for (var i = Contents.Count - 1; i >= 0; i--)// iterate backwards because of remove swap. + { + var reagent = Contents[i]; + + // This is set up such that integer rounding will tend to take more reagents. + var split = remaining * reagent.Quantity.Value / effVol; + + if (split <= 0) + { + effVol -= reagent.Quantity.Value; + DebugTools.Assert(split == 0, "Negative solution quantity while splitting? Long/int overflow?"); + continue; + } + + var splitQuantity = FixedPoint2.FromCents((int) split); + var newQuantity = reagent.Quantity - splitQuantity; + + if (newQuantity > FixedPoint2.Zero) + Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); + else + Contents.RemoveSwap(i); + + remaining -= split; + effVol -= reagent.Quantity.Value; + } + + DebugTools.Assert(remaining >= 0); + DebugTools.Assert(remaining == 0 || Volume == FixedPoint2.Zero); + + _heatCapacityDirty = true; + ValidateSolution(); + } + + public void AddSolution(Solution otherSolution, IPrototypeManager? protoMan) + { + if (otherSolution.Volume <= FixedPoint2.Zero) + return; + + Volume += otherSolution.Volume; + + var closeTemps = MathHelper.CloseTo(otherSolution.Temperature, Temperature); + float totalThermalEnergy = 0; + if (!closeTemps) + { + IoCManager.Resolve(ref protoMan); + + if (_heatCapacityDirty) + UpdateHeatCapacity(protoMan); + + if (otherSolution._heatCapacityDirty) + otherSolution.UpdateHeatCapacity(protoMan); + + totalThermalEnergy = _heatCapacity * Temperature + otherSolution._heatCapacity * otherSolution.Temperature; + } + for (var i = 0; i < otherSolution.Contents.Count; i++) { var otherReagent = otherSolution.Contents[i]; @@ -323,65 +590,50 @@ namespace Content.Shared.Chemistry.Components } } - TotalVolume += otherSolution.TotalVolume; - ThermalEnergy = oldThermalEnergy + addedThermalEnergy; + _heatCapacity += otherSolution._heatCapacity; + if (closeTemps) + _heatCapacityDirty |= otherSolution._heatCapacityDirty; + else + Temperature = _heatCapacity == 0 ? 0 : totalThermalEnergy / _heatCapacity; + + ValidateSolution(); } - private Color GetColor() + public Color GetColor(IPrototypeManager? protoMan) { - if (TotalVolume == 0) + if (Volume == FixedPoint2.Zero) { return Color.Transparent; } + IoCManager.Resolve(ref protoMan); + Color mixColor = default; var runningTotalQuantity = FixedPoint2.New(0); - var protoManager = IoCManager.Resolve(); + bool first = true; foreach (var reagent in Contents) { runningTotalQuantity += reagent.Quantity; - if (!protoManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) + if (!protoMan.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) { continue; } - if (mixColor == default) + if (first) { + first = false; mixColor = proto.SubstanceColor; continue; } - var interpolateValue = (1 / runningTotalQuantity.Float()) * reagent.Quantity.Float(); + var interpolateValue = reagent.Quantity.Float() / runningTotalQuantity.Float(); mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor, interpolateValue); } return mixColor; } - public Solution Clone() - { - var volume = FixedPoint2.New(0); - var heatCapacity = 0.0d; - var newSolution = new Solution(); - var prototypeManager = IoCManager.Resolve(); - - for (var i = 0; i < Contents.Count; i++) - { - var reagent = Contents[i]; - if (!prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) - proto = new ReagentPrototype(); - - newSolution.Contents.Add(reagent); - volume += reagent.Quantity; - heatCapacity += (float) reagent.Quantity * proto.SpecificHeat; - } - - newSolution.TotalVolume = volume; - newSolution.Temperature = Temperature; - return newSolution; - } - [Obsolete("Use ReactiveSystem.DoEntityReaction")] public void DoEntityReaction(EntityUid uid, ReactionMethod method) { @@ -429,7 +681,23 @@ namespace Content.Shared.Chemistry.Components { return GetEnumerator(); } - #endregion + + public void SetContents(IEnumerable reagents, bool setMaxVol = false) + { + RemoveAllSolution(); + _heatCapacityDirty = true; + Contents = new(reagents); + foreach (var reagent in Contents) + { + Volume += reagent.Quantity; + } + + if (setMaxVol) + MaxVolume = Volume; + + ValidateSolution(); + } + } } diff --git a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs index dd2e668c0e..df9346556e 100644 --- a/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs +++ b/Content.Shared/Chemistry/Reaction/SharedChemicalReactionSystem.cs @@ -140,7 +140,7 @@ namespace Content.Shared.Chemistry.Reaction var reactantName = reactantData.Key; var reactantCoefficient = reactantData.Value.Amount; - if (!solution.ContainsReagent(reactantName, out var reactantQuantity)) + if (!solution.TryGetReagent(reactantName, out var reactantQuantity)) return false; if (reactantData.Value.Catalyst) @@ -252,12 +252,12 @@ namespace Content.Shared.Chemistry.Reaction // Remove any reactions that were not applicable. Avoids re-iterating over them in future. reactions.Except(toRemove); - if (products.TotalVolume <= 0) + if (products.Volume <= 0) return true; // remove excess product // TODO spill excess? - var excessVolume = solution.TotalVolume + products.TotalVolume - maxVolume; + var excessVolume = solution.Volume + products.Volume - maxVolume; if (excessVolume > 0) products.RemoveSolution(excessVolume); @@ -269,7 +269,7 @@ namespace Content.Shared.Chemistry.Reaction reactions.UnionWith(reactantReactions); } - solution.AddSolution(products); + solution.AddSolution(products, _prototypeManager); return true; } diff --git a/Content.Shared/FixedPoint/FixedPoint2.cs b/Content.Shared/FixedPoint/FixedPoint2.cs index bb48f911be..74ffdf291a 100644 --- a/Content.Shared/FixedPoint/FixedPoint2.cs +++ b/Content.Shared/FixedPoint/FixedPoint2.cs @@ -11,7 +11,7 @@ namespace Content.Shared.FixedPoint [Serializable, CopyByRef] public struct FixedPoint2 : ISelfSerialize, IComparable, IEquatable, IFormattable { - private int _value; + public int Value { get; private set; } private const int Shift = 2; public static FixedPoint2 MaxValue { get; } = new(int.MaxValue); @@ -20,12 +20,12 @@ namespace Content.Shared.FixedPoint private readonly double ShiftDown() { - return _value / Math.Pow(10, Shift); + return Value / Math.Pow(10, Shift); } private FixedPoint2(int value) { - _value = value; + Value = value; } public static FixedPoint2 New(int value) @@ -33,6 +33,8 @@ namespace Content.Shared.FixedPoint return new(value * (int) Math.Pow(10, Shift)); } + public static FixedPoint2 FromCents(int value) => new(value); + public static FixedPoint2 New(float value) { return new(FromFloat(value)); @@ -60,42 +62,42 @@ namespace Content.Shared.FixedPoint public static FixedPoint2 operator +(FixedPoint2 a) => a; - public static FixedPoint2 operator -(FixedPoint2 a) => new(-a._value); + public static FixedPoint2 operator -(FixedPoint2 a) => new(-a.Value); public static FixedPoint2 operator +(FixedPoint2 a, FixedPoint2 b) - => new(a._value + b._value); + => new(a.Value + b.Value); public static FixedPoint2 operator -(FixedPoint2 a, FixedPoint2 b) - => new(a._value - b._value); + => new(a.Value - b.Value); public static FixedPoint2 operator *(FixedPoint2 a, FixedPoint2 b) { - return new((int) MathF.Round(b._value * a._value / MathF.Pow(10, Shift), MidpointRounding.AwayFromZero)); + return new((int) MathF.Round(b.Value * a.Value / MathF.Pow(10, Shift), MidpointRounding.AwayFromZero)); } public static FixedPoint2 operator *(FixedPoint2 a, float b) { - return new((int) MathF.Round(a._value * b, MidpointRounding.AwayFromZero)); + return new((int) MathF.Round(a.Value * b, MidpointRounding.AwayFromZero)); } public static FixedPoint2 operator *(FixedPoint2 a, double b) { - return new((int) Math.Round(a._value * b, MidpointRounding.AwayFromZero)); + return new((int) Math.Round(a.Value * b, MidpointRounding.AwayFromZero)); } public static FixedPoint2 operator *(FixedPoint2 a, int b) { - return new(a._value * b); + return new(a.Value * b); } public static FixedPoint2 operator /(FixedPoint2 a, FixedPoint2 b) { - return new((int) MathF.Round((MathF.Pow(10, Shift) * a._value) / b._value, MidpointRounding.AwayFromZero)); + return new((int) MathF.Round((MathF.Pow(10, Shift) * a.Value) / b.Value, MidpointRounding.AwayFromZero)); } public static FixedPoint2 operator /(FixedPoint2 a, float b) { - return new((int) MathF.Round(a._value / b, MidpointRounding.AwayFromZero)); + return new((int) MathF.Round(a.Value / b, MidpointRounding.AwayFromZero)); } public static bool operator <=(FixedPoint2 a, int b) @@ -140,22 +142,22 @@ namespace Content.Shared.FixedPoint public static bool operator <=(FixedPoint2 a, FixedPoint2 b) { - return a._value <= b._value; + return a.Value <= b.Value; } public static bool operator >=(FixedPoint2 a, FixedPoint2 b) { - return a._value >= b._value; + return a.Value >= b.Value; } public static bool operator <(FixedPoint2 a, FixedPoint2 b) { - return a._value < b._value; + return a.Value < b.Value; } public static bool operator >(FixedPoint2 a, FixedPoint2 b) { - return a._value > b._value; + return a.Value > b.Value; } public readonly float Float() @@ -199,7 +201,7 @@ namespace Content.Shared.FixedPoint public static FixedPoint2 Abs(FixedPoint2 a) { - return FixedPoint2.New(Math.Abs(a._value)); + return FixedPoint2.New(Math.Abs(a.Value)); } public static FixedPoint2 Dist(FixedPoint2 a, FixedPoint2 b) @@ -220,18 +222,18 @@ namespace Content.Shared.FixedPoint public override readonly bool Equals(object? obj) { return obj is FixedPoint2 unit && - _value == unit._value; + Value == unit.Value; } public override readonly int GetHashCode() { // ReSharper disable once NonReadonlyMemberInGetHashCode - return HashCode.Combine(_value); + return HashCode.Combine(Value); } public void Deserialize(string value) { - _value = FromFloat(FloatFromString(value)); + Value = FromFloat(FloatFromString(value)); } public override readonly string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}"; @@ -248,16 +250,16 @@ namespace Content.Shared.FixedPoint public readonly bool Equals(FixedPoint2 other) { - return _value == other._value; + return Value == other.Value; } public readonly int CompareTo(FixedPoint2 other) { - if (other._value > _value) + if (other.Value > Value) { return -1; } - if (other._value < _value) + if (other.Value < Value) { return 1; } diff --git a/Content.Tests/Shared/Chemistry/Solution_Tests.cs b/Content.Tests/Shared/Chemistry/Solution_Tests.cs index f6008ed26e..9b88f6d8f0 100644 --- a/Content.Tests/Shared/Chemistry/Solution_Tests.cs +++ b/Content.Tests/Shared/Chemistry/Solution_Tests.cs @@ -43,6 +43,7 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(quantity.Int(), Is.EqualTo(0)); } +#if !DEBUG [Test] public void AddLessThanZeroReagentReturnsZero() { @@ -51,6 +52,7 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(quantity.Int(), Is.EqualTo(0)); } +#endif [Test] public void AddingReagentsSumsProperly() @@ -81,7 +83,7 @@ public sealed class Solution_Tests : ContentUnitTest solution.AddReagent("water", FixedPoint2.New(1000)); solution.AddReagent("fire", FixedPoint2.New(2000)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(3000)); + Assert.That(solution.Volume.Int(), Is.EqualTo(3000)); } [Test] @@ -95,7 +97,7 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(newSolution.GetReagentQuantity("water").Int(), Is.EqualTo(1000)); Assert.That(newSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); - Assert.That(newSolution.TotalVolume.Int(), Is.EqualTo(3000)); + Assert.That(newSolution.Volume.Int(), Is.EqualTo(3000)); } [Test] @@ -109,7 +111,7 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500)); Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2500)); + Assert.That(solution.Volume.Int(), Is.EqualTo(2500)); } [Test] @@ -120,7 +122,7 @@ public sealed class Solution_Tests : ContentUnitTest solution.RemoveReagent("water", FixedPoint2.New(-100)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100)); + Assert.That(solution.Volume.Int(), Is.EqualTo(100)); } [Test] @@ -131,7 +133,7 @@ public sealed class Solution_Tests : ContentUnitTest solution.RemoveReagent("water", FixedPoint2.New(1000)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); + Assert.That(solution.Volume.Int(), Is.EqualTo(0)); } [Test] @@ -142,7 +144,7 @@ public sealed class Solution_Tests : ContentUnitTest solution.RemoveReagent("fire", FixedPoint2.New(1000)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100)); + Assert.That(solution.Volume.Int(), Is.EqualTo(100)); } [Test] @@ -154,7 +156,7 @@ public sealed class Solution_Tests : ContentUnitTest //Check that edited solution is correct Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(200)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(200)); + Assert.That(solution.Volume.Int(), Is.EqualTo(200)); } [Test] @@ -166,7 +168,7 @@ public sealed class Solution_Tests : ContentUnitTest //Check that edited solution is correct Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); + Assert.That(solution.Volume.Int(), Is.EqualTo(0)); } [Test] @@ -180,7 +182,7 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500)); Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1000)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1500)); + Assert.That(solution.Volume.Int(), Is.EqualTo(1500)); } [Test] @@ -191,7 +193,7 @@ public sealed class Solution_Tests : ContentUnitTest solution.RemoveSolution(FixedPoint2.New(-200)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800)); + Assert.That(solution.Volume.Int(), Is.EqualTo(800)); } [Test] @@ -205,11 +207,11 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(750)); Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1500)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2250)); + Assert.That(solution.Volume.Int(), Is.EqualTo(2250)); Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(250)); Assert.That(splitSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(500)); - Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(750)); + Assert.That(splitSolution.Volume.Int(), Is.EqualTo(750)); } [Test] @@ -221,13 +223,13 @@ public sealed class Solution_Tests : ContentUnitTest var splitSolution = solution.SplitSolution(FixedPoint2.New(1)); - Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f)); - Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2)); + Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.66f)); + Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.34f)); + Assert.That(solution.Volume.Int(), Is.EqualTo(2)); - Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f)); - Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f)); - Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(1)); + Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.34f)); + Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.66f)); + Assert.That(splitSolution.Volume.Int(), Is.EqualTo(1)); } [Test] @@ -241,11 +243,11 @@ public sealed class Solution_Tests : ContentUnitTest Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f)); Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1)); + Assert.That(solution.Volume.Int(), Is.EqualTo(1)); Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f)); Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f)); - Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(2)); + Assert.That(splitSolution.Volume.Int(), Is.EqualTo(2)); } [Test] @@ -259,10 +261,10 @@ public sealed class Solution_Tests : ContentUnitTest var splitSolution = solution.SplitSolution(FixedPoint2.New(reduce)); Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(remainder)); - Assert.That(solution.TotalVolume.Float(), Is.EqualTo(remainder)); + Assert.That(solution.Volume.Float(), Is.EqualTo(remainder)); Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(reduce)); - Assert.That(splitSolution.TotalVolume.Float(), Is.EqualTo(reduce)); + Assert.That(splitSolution.Volume.Float(), Is.EqualTo(reduce)); } [Test] @@ -280,7 +282,7 @@ public sealed class Solution_Tests : ContentUnitTest var splitAmount = FixedPoint2.New(5); var split = solutionOne.SplitSolution(splitAmount); - Assert.That(split.TotalVolume, Is.EqualTo(splitAmount)); + Assert.That(split.Volume, Is.EqualTo(splitAmount)); } [Test] @@ -291,10 +293,10 @@ public sealed class Solution_Tests : ContentUnitTest var splitSolution = solution.SplitSolution(FixedPoint2.New(1000)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); + Assert.That(solution.Volume.Int(), Is.EqualTo(0)); Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); - Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(800)); + Assert.That(splitSolution.Volume.Int(), Is.EqualTo(800)); } [Test] @@ -305,10 +307,10 @@ public sealed class Solution_Tests : ContentUnitTest var splitSolution = solution.SplitSolution(FixedPoint2.New(-200)); Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); - Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800)); + Assert.That(solution.Volume.Int(), Is.EqualTo(800)); Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); - Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(0)); + Assert.That(splitSolution.Volume.Int(), Is.EqualTo(0)); } [Test] @@ -343,12 +345,12 @@ public sealed class Solution_Tests : ContentUnitTest solutionTwo.AddReagent("water", FixedPoint2.New(500)); solutionTwo.AddReagent("earth", FixedPoint2.New(1000)); - solutionOne.AddSolution(solutionTwo); + solutionOne.AddSolution(solutionTwo, null); Assert.That(solutionOne.GetReagentQuantity("water").Int(), Is.EqualTo(1500)); Assert.That(solutionOne.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); Assert.That(solutionOne.GetReagentQuantity("earth").Int(), Is.EqualTo(1000)); - Assert.That(solutionOne.TotalVolume.Int(), Is.EqualTo(4500)); + Assert.That(solutionOne.Volume.Int(), Is.EqualTo(4500)); } // Tests concerning thermal energy and temperature. @@ -359,24 +361,7 @@ public sealed class Solution_Tests : ContentUnitTest public void EmptySolutionHasNoHeatCapacity() { var solution = new Solution(); - Assert.That(solution.HeatCapacity, Is.EqualTo(0.0f)); - } - - [Test] - public void EmptySolutionHasNoThermalEnergy() - { - var solution = new Solution(); - Assert.That(solution.ThermalEnergy, Is.EqualTo(0.0f)); - } - - [Test] - public void AddReagentToEmptySolutionSetsTemperature() - { - const float testTemp = 100.0f; - - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), testTemp); - Assert.That(solution.Temperature, Is.EqualTo(testTemp)); + Assert.That(solution.GetHeatCapacity(null), Is.EqualTo(0.0f)); } [Test] @@ -384,8 +369,7 @@ public sealed class Solution_Tests : ContentUnitTest { const float initialTemp = 100.0f; - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), initialTemp); + var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp }; solution.AddReagent("water", FixedPoint2.New(100)); Assert.That(solution.Temperature, Is.EqualTo(initialTemp)); @@ -408,7 +392,7 @@ public sealed class Solution_Tests : ContentUnitTest solutionTwo.AddReagent("earth", FixedPoint2.New(100)); solutionTwo.Temperature = initialTemp; - solutionOne.AddSolution(solutionTwo); + solutionOne.AddSolution(solutionTwo, null); Assert.That(solutionOne.Temperature, Is.EqualTo(initialTemp)); } @@ -417,8 +401,7 @@ public sealed class Solution_Tests : ContentUnitTest { const float initialTemp = 100.0f; - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), initialTemp); + var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp }; solution.RemoveReagent("water", FixedPoint2.New(50)); Assert.That(solution.Temperature, Is.EqualTo(initialTemp)); } @@ -428,8 +411,7 @@ public sealed class Solution_Tests : ContentUnitTest { const float initialTemp = 100.0f; - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), initialTemp); + var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp }; solution.RemoveSolution(FixedPoint2.New(50)); Assert.That(solution.Temperature, Is.EqualTo(initialTemp)); } @@ -439,45 +421,10 @@ public sealed class Solution_Tests : ContentUnitTest { const float initialTemp = 100.0f; - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), initialTemp); + var solution = new Solution("water", FixedPoint2.New(100)) { Temperature = initialTemp }; solution.SplitSolution(FixedPoint2.New(50)); Assert.That(solution.Temperature, Is.EqualTo(initialTemp)); } - [Test] - public void AddReagentWithSetTemperatureAdjustsTemperature() - { - const float temp = 100.0f; - - var solution = new Solution(); - solution.AddReagent("water", FixedPoint2.New(100), temp * 1); - Assert.That(solution.Temperature, Is.EqualTo(temp * 1)); - - solution.AddReagent("water", FixedPoint2.New(100), temp * 3); - Assert.That(solution.Temperature, Is.EqualTo(temp * 2)); - - solution.AddReagent("earth", FixedPoint2.New(100), temp * 5); - Assert.That(solution.Temperature, Is.EqualTo(temp * 3)); - } - - [Test] - public void AddSolutionCombinesThermalEnergy() - { - const float initialTemp = 100.0f; - - var solutionOne = new Solution(); - solutionOne.AddReagent("water", FixedPoint2.New(100), initialTemp); - - var solutionTwo = new Solution(); - solutionTwo.AddReagent("water", FixedPoint2.New(100), initialTemp); - solutionTwo.AddReagent("earth", FixedPoint2.New(100)); - - var thermalEnergyOne = solutionOne.ThermalEnergy; - var thermalEnergyTwo = solutionTwo.ThermalEnergy; - solutionOne.AddSolution(solutionTwo); - Assert.That(solutionOne.ThermalEnergy, Is.EqualTo(thermalEnergyOne + thermalEnergyTwo)); - } - #endregion Thermal Energy and Temperature } diff --git a/Resources/Prototypes/Body/Organs/diona.yml b/Resources/Prototypes/Body/Organs/diona.yml index 9015d1799e..7e60153673 100644 --- a/Resources/Prototypes/Body/Organs/diona.yml +++ b/Resources/Prototypes/Body/Organs/diona.yml @@ -13,6 +13,7 @@ - type: SolutionContainerManager solutions: organ: + maxVol: 10 reagents: - ReagentId: Nutriment Quantity: 10 @@ -32,10 +33,12 @@ - type: SolutionContainerManager solutions: organ: + maxVol: 10 reagents: - ReagentId: Nutriment Quantity: 10 Lung: + maxVol: 100 canReact: False - type: MovementSpeedModifier baseWalkSpeed: 0 @@ -76,7 +79,7 @@ - type: SolutionContainerManager solutions: stomach: - maxVol: 250 + maxVol: 50 - type: Stomach - type: Metabolizer maxReagents: 6 diff --git a/Resources/Prototypes/Body/Organs/human.yml b/Resources/Prototypes/Body/Organs/human.yml index 9c2682d026..e92c426a1c 100644 --- a/Resources/Prototypes/Body/Organs/human.yml +++ b/Resources/Prototypes/Body/Organs/human.yml @@ -141,7 +141,7 @@ - type: SolutionContainerManager solutions: stomach: - maxVol: 250 + maxVol: 50 - type: Stomach # The stomach metabolizes stuff like foods and drinks. # TODO: Have it work off of the ent's solution container, and move this diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index b12774551d..7cf62880c4 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -9,7 +9,7 @@ drawdepth: FloorObjects - type: SolutionContainerManager solutions: - puddle: {} + puddle: { maxVol: 1000 } - type: Puddle spillSound: path: /Audio/Effects/Fluids/splat.ogg @@ -53,6 +53,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: Water Quantity: 10 @@ -140,6 +141,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: Nutriment Quantity: 5 @@ -166,6 +168,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: Toxin Quantity: 5 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml index 1b1d6392c4..1e04cc812a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml @@ -21,7 +21,7 @@ - ReagentId: Nutriment Quantity: 11 - ReagentId: Vitamin - Quanity: 5 + Quantity: 5 - type: SliceableFood count: 4 - type: Tag diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml index ebd86d0572..41eb9ca924 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/egg.yml @@ -81,6 +81,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: Egg Quantity: 2 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml index 9fef48164b..5f15ae814c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -17,6 +17,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: Flour Quantity: 10 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 10d6e4868e..758c06cb79 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -410,6 +410,7 @@ - type: SolutionContainerManager solutions: puddle: + maxVol: 1000 reagents: - ReagentId: JuiceTomato Quantity: 10