]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Metabolizing bloodstream (#35071)
authorNikovnik <116634167+nkokic@users.noreply.github.com>
Wed, 17 Dec 2025 19:21:16 +0000 (20:21 +0100)
committerGitHub <noreply@github.com>
Wed, 17 Dec 2025 19:21:16 +0000 (19:21 +0000)
* merged chemical into bloodstream

* changed injectable to bloodstream

* separated bleeding and direct blood removal

* removed blood gain from protein

* reduced blood gain from saline

* rejuvenating fills to reference volume

* fixed blood regulation

* red mead requires stirring to make

* reverted accidental line deletion

* cleared the skeletons from the closet

* additional routing

* field rename for xeno

* removed mention of chemstream and field rename for asteroid mobs

* minor optimizations

* Revert "reduced blood gain from saline"

This reverts commit de26fd1c0d99f3019fe7dd1451a50230cc90f058.

* Revert "removed blood gain from protein"

This reverts commit 7a1648caf39fe26406db73c2a5afa389b82c612f.

* removed unused component fetch

* dead check mini refactor

* eventized blood exclusion

* quick fix

* Pain

* Commit of doom

* COMMIT

* renamed bloodMaxFactor to MaxVolumeFactor

* addressed floating point error

* returned vomiting chemicals

* blood reagent always skips the flush

* no need to mention blood reagent

* fixed passing blood flush

* adadsafasfasfassfasf

* whoops

* merge fixed injectors

* Revert "adadsafasfasfassfasf"

This reverts commit 0a5313a68dd6484d36d28d08930c76851b72ae38.

* simplify reagent removal

* enabled foreign blood transfusion

* Revert "COMMIT"

This reverts commit 19abd679cd7761ebd47bb242bd644176a3006a42.

* simplified reagent removal when modifying blood level

* removed misleading coment since the changes

* documented MetabolismExclusionEvent

* fixed negative negative modification of blood level

* fixed hypervolemia not normalizing

* constrainted blood modification

* returned bloodpack stop on fully healed

* forgot to stage this

* band aid for diona blood

* swapping GetReagent with GetPrototype

* optimize blood filtering

* multiplicative multi reagent blood level calculation

* removed unused stuff

* optimized blood calculation a tiny bit

* added per reagent blood regulation

* optimized (referenceVolume + bloodReagents) into referenceSolution

* polished coded to proper function

* forgot to stage rootable system change

* clean up, unnecessary GetBloodLevel call

* rename method name to TryAddToBloodstream instead of Chemicals

* placed overfill safety

* cleanup and final touches

* final touch

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
42 files changed:
Content.Server/Body/Components/MetabolizerComponent.cs
Content.Server/Body/Systems/BloodstreamSystem.cs
Content.Server/Body/Systems/MetabolizerSystem.cs
Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs
Content.Server/Fluids/EntitySystems/SmokeSystem.cs
Content.Server/Medical/HealthAnalyzerSystem.cs
Content.Server/Nutrition/EntitySystems/SmokingSystem.cs
Content.Server/Zombies/ZombieSystem.Transform.cs
Content.Shared/Body/Components/BloodstreamComponent.cs
Content.Shared/Body/Components/StomachComponent.cs
Content.Shared/Body/Events/MetabolismExclusionEvent.cs [new file with mode: 0644]
Content.Shared/Body/Systems/SharedBloodstreamSystem.cs
Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs
Content.Shared/Devour/DevourSystem.cs
Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs
Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
Content.Shared/Medical/Healing/HealingSystem.cs
Content.Shared/Medical/VomitSystem.cs
Content.Shared/Rootable/RootableSystem.cs
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml
Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml
Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml
Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml
Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml
Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml
Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml
Resources/Prototypes/Entities/Mobs/NPCs/space.yml
Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml
Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
Resources/Prototypes/Entities/Mobs/Player/dragon.yml
Resources/Prototypes/Entities/Mobs/Species/arachnid.yml
Resources/Prototypes/Entities/Mobs/Species/diona.yml
Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml
Resources/Prototypes/Entities/Mobs/Species/moth.yml
Resources/Prototypes/Entities/Mobs/Species/slime.yml
Resources/Prototypes/Entities/Mobs/Species/vox.yml
Resources/Prototypes/Entities/Mobs/base.yml
Resources/Prototypes/Recipes/Reactions/drinks.yml
Resources/Prototypes/Recipes/Reactions/fun.yml
Resources/Prototypes/Recipes/Reactions/single_reagent.yml

index 46d2fdd8e80ddb1c7777da324a1b966ac75df16e..2401db5aac0dcc069c3e35a898210c6f2e6dc9a9 100644 (file)
@@ -42,7 +42,7 @@ namespace Content.Server.Body.Components
         ///     From which solution will this metabolizer attempt to metabolize chemicals
         /// </summary>
         [DataField("solution")]
-        public string SolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
+        public string SolutionName = BloodstreamComponent.DefaultBloodSolutionName;
 
         /// <summary>
         ///     Does this component use a solution on it's parent entity (the body) or itself
index a58deec49424fd74c0222c8d41b68403b4916333..08f640711ae814b8fcf4598639b6fb2b12bfc1b5 100644 (file)
@@ -21,9 +21,6 @@ public sealed class BloodstreamSystem : SharedBloodstreamSystem
     private void OnComponentInit(Entity<BloodstreamComponent> entity, ref ComponentInit args)
     {
         if (!SolutionContainer.EnsureSolution(entity.Owner,
-                entity.Comp.ChemicalSolutionName,
-                out var chemicalSolution) ||
-            !SolutionContainer.EnsureSolution(entity.Owner,
                 entity.Comp.BloodSolutionName,
                 out var bloodSolution) ||
             !SolutionContainer.EnsureSolution(entity.Owner,
@@ -31,14 +28,13 @@ public sealed class BloodstreamSystem : SharedBloodstreamSystem
                 out var tempSolution))
             return;
 
-        chemicalSolution.MaxVolume = entity.Comp.ChemicalMaxVolume;
-        bloodSolution.MaxVolume = entity.Comp.BloodMaxVolume;
+        bloodSolution.MaxVolume = entity.Comp.BloodReferenceSolution.Volume * entity.Comp.MaxVolumeModifier;
         tempSolution.MaxVolume = entity.Comp.BleedPuddleThreshold * 4; // give some leeway, for chemstream as well
 
         // Fill blood solution with BLOOD
         // The DNA string might not be initialized yet, but the reagent data gets updated in the GenerateDnaEvent subscription
-        var solution = entity.Comp.BloodReagents.Clone();
-        solution.ScaleTo(entity.Comp.BloodMaxVolume - bloodSolution.Volume);
+        var solution = entity.Comp.BloodReferenceSolution.Clone();
+        solution.ScaleTo(entity.Comp.BloodReferenceSolution.Volume - bloodSolution.Volume);
         solution.SetReagentData(GetEntityBloodData(entity.Owner));
         bloodSolution.AddSolution(solution, PrototypeManager);
     }
index b5b30ae74cf9109324362a0b4817d40444be7df7..35c7b0572a30be6b41c90c82fe4df27595e63bb8 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Server.Body.Components;
 using Content.Shared.Body.Events;
 using Content.Shared.Body.Organ;
@@ -14,7 +15,6 @@ using Content.Shared.EntityEffects;
 using Content.Shared.EntityEffects.Effects.Body;
 using Content.Shared.EntityEffects.Effects.Solution;
 using Content.Shared.FixedPoint;
-using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Content.Shared.Random.Helpers;
 using Robust.Shared.Collections;
@@ -134,17 +134,30 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
             return;
         }
 
+        // Copy the solution do not edit the original solution list
+        var list = solution.Contents.ToList();
+
+        // Collecting blood reagent for filtering
+        var bloodList = new List<string>();
+        var ev = new MetabolismExclusionEvent(bloodList);
+        RaiseLocalEvent(solutionEntityUid.Value, ref ev);
+
         // randomize the reagent list so we don't have any weird quirks
         // like alphabetical order or insertion order mattering for processing
-        var list = solution.Contents.ToArray();
         _random.Shuffle(list);
 
+        bool isDead = _mobStateSystem.IsDead(solutionEntityUid.Value);
+
         int reagents = 0;
         foreach (var (reagent, quantity) in list)
         {
             if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
                 continue;
 
+            // Skip blood reagents
+            if (bloodList.Contains(reagent.Prototype))
+                continue;
+
             var mostToRemove = FixedPoint2.Zero;
             if (proto.Metabolisms is null)
             {
@@ -186,11 +199,8 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
                 // if it's possible for them to be dead, and they are,
                 // then we shouldn't process any effects, but should probably
                 // still remove reagents
-                if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
-                {
-                    if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
-                        continue;
-                }
+                if (isDead && !proto.WorksOnTheDead)
+                    continue;
 
                 var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
 
index 7b4deea9f40c3de95ecbf105fc230441db1ea04c..e6e16422957838f79af1af38e9d4c7800d45dc08 100644 (file)
@@ -148,7 +148,7 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem
             // Take our portion of the adjusted solution for this target
             var individualInjection = solutionToInject.SplitSolution(volumePerBloodstream);
             // Inject our portion into the target's bloodstream
-            if (_bloodstream.TryAddToChemicals(targetBloodstream.AsNullable(), individualInjection))
+            if (_bloodstream.TryAddToBloodstream(targetBloodstream.AsNullable(), individualInjection))
                 anySuccess = true;
         }
 
index 7c9d02c5619ecc7a00c169fb4dfcc8a9ad3f0451..c787132504fad183e5fbc7d086bfb6ee555b14b1 100644 (file)
@@ -264,14 +264,14 @@ public sealed class SmokeSystem : EntitySystem
         if (!TryComp<BloodstreamComponent>(entity, out var bloodstream))
             return;
 
-        if (!_solutionContainerSystem.ResolveSolution(entity, bloodstream.ChemicalSolutionName, ref bloodstream.ChemicalSolution, out var chemSolution) || chemSolution.AvailableVolume <= 0)
+        if (!_solutionContainerSystem.ResolveSolution(entity, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution) || bloodSolution.AvailableVolume <= 0)
             return;
 
         var blockIngestion = _internals.AreInternalsWorking(entity);
 
         var cloneSolution = solution.Clone();
         var availableTransfer = FixedPoint2.Min(cloneSolution.Volume, component.TransferRate);
-        var transferAmount = FixedPoint2.Min(availableTransfer, chemSolution.AvailableVolume);
+        var transferAmount = FixedPoint2.Min(availableTransfer, bloodSolution.AvailableVolume);
         var transferSolution = cloneSolution.SplitSolution(transferAmount);
 
         foreach (var reagentQuantity in transferSolution.Contents.ToArray())
@@ -287,7 +287,7 @@ public sealed class SmokeSystem : EntitySystem
         if (blockIngestion)
             return;
 
-        if (_blood.TryAddToChemicals((entity, bloodstream), transferSolution))
+        if (_blood.TryAddToBloodstream((entity, bloodstream), transferSolution))
         {
             // Log solution addition by smoke
             _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(entity):target} ingested smoke {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}");
index bc6285c1d216c4be5bc4d29de289a3a40d111cad..b7f48cade49d3dda181da6980d99066d6c8449ec 100644 (file)
@@ -18,6 +18,7 @@ using Robust.Server.GameObjects;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Containers;
 using Robust.Shared.Timing;
+using Content.Server.Body.Systems;
 
 namespace Content.Server.Medical;
 
@@ -32,6 +33,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
     [Dependency] private readonly TransformSystem _transformSystem = default!;
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
 
     public override void Initialize()
     {
@@ -204,7 +206,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
             _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName,
                 ref bloodstream.BloodSolution, out var bloodSolution))
         {
-            bloodAmount = bloodSolution.FillFraction;
+            bloodAmount = _bloodstreamSystem.GetBloodLevel(target);
             bleeding = bloodstream.BleedAmount > 0;
         }
 
index 4960ff1ba84603fd0c91197d5d593bf199c946bf..ae35fa4825189189d9b5d647ba4297dcf8577fb5 100644 (file)
@@ -157,7 +157,7 @@ namespace Content.Server.Nutrition.EntitySystems
                 }
 
                 _reactiveSystem.DoEntityReaction(containerManager.Owner, inhaledSolution, ReactionMethod.Ingestion);
-                _bloodstreamSystem.TryAddToChemicals((containerManager.Owner, bloodstream), inhaledSolution);
+                _bloodstreamSystem.TryAddToBloodstream((containerManager.Owner, bloodstream), inhaledSolution);
             }
 
             _timer -= UpdateTimer;
index cfa22b2a141b659639c7a45143ba50e9e786db80..b4aee77cef653a825b13b916fc120a7905199e0c 100644 (file)
@@ -193,7 +193,7 @@ public sealed partial class ZombieSystem
             zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor;
             zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor;
             zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers);
-            if (TryComp<BloodstreamComponent>(target, out var stream) && stream.BloodReagents is { } reagents)
+            if (TryComp<BloodstreamComponent>(target, out var stream) && stream.BloodReferenceSolution is { } reagents)
                 zombiecomp.BeforeZombifiedBloodReagents = reagents.Clone();
 
             _humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp);
index 2ebfae6f33ac93d9ca914b2fe75b0ca0efe51542..8309397b621b64079e57ca0169bb00f6d5bbd7cc 100644 (file)
@@ -19,7 +19,6 @@ namespace Content.Shared.Body.Components;
 [Access(typeof(SharedBloodstreamSystem))]
 public sealed partial class BloodstreamComponent : Component
 {
-    public const string DefaultChemicalsSolutionName = "chemicals";
     public const string DefaultBloodSolutionName = "bloodstream";
     public const string DefaultBloodTemporarySolutionName = "bloodstreamTemporary";
 
@@ -138,26 +137,19 @@ public sealed partial class BloodstreamComponent : Component
     // TODO probably damage bleed thresholds.
 
     /// <summary>
-    /// Max volume of internal chemical solution storage
+    /// Modifier applied to <see cref="BloodReferenceSolution.Volume"/> to determine maximum volume for bloodstream.
     /// </summary>
     [DataField]
-    public FixedPoint2 ChemicalMaxVolume = FixedPoint2.New(250);
+    public float MaxVolumeModifier = 2f;
 
     /// <summary>
-    /// Max volume of internal blood storage,
-    /// and starting level of blood.
-    /// </summary>
-    [DataField]
-    public FixedPoint2 BloodMaxVolume = FixedPoint2.New(300);
-
-    /// <summary>
-    /// Which reagents are considered this entities 'blood'?
+    /// Defines which reagents are considered as 'blood' and how much of it is normal.
     /// </summary>
     /// <remarks>
     /// Slime-people might use slime as their blood or something like that.
     /// </remarks>
     [DataField, AutoNetworkedField]
-    public Solution BloodReagents = new([new("Blood", 1)]);
+    public Solution BloodReferenceSolution = new([new("Blood", 300)]);
 
     /// <summary>
     /// Name/Key that <see cref="BloodSolution"/> is indexed by.
@@ -165,12 +157,6 @@ public sealed partial class BloodstreamComponent : Component
     [DataField]
     public string BloodSolutionName = DefaultBloodSolutionName;
 
-    /// <summary>
-    /// Name/Key that <see cref="ChemicalSolution"/> is indexed by.
-    /// </summary>
-    [DataField]
-    public string ChemicalSolutionName = DefaultChemicalsSolutionName;
-
     /// <summary>
     /// Name/Key that <see cref="TemporarySolution"/> is indexed by.
     /// </summary>
@@ -183,12 +169,6 @@ public sealed partial class BloodstreamComponent : Component
     [ViewVariables]
     public Entity<SolutionComponent>? BloodSolution;
 
-    /// <summary>
-    /// Internal solution for reagent storage
-    /// </summary>
-    [ViewVariables]
-    public Entity<SolutionComponent>? ChemicalSolution;
-
     /// <summary>
     /// Temporary blood solution.
     /// When blood is lost, it goes to this solution, and when this
index ca20cc544a815d8e2337d2338ade480501af15fe..e1413de9e441e4f2bcc53951cbe943951e809c9c 100644 (file)
@@ -45,7 +45,7 @@ namespace Content.Shared.Body.Components
         ///     What solution should this stomach push reagents into, on the body?
         /// </summary>
         [DataField]
-        public string BodySolutionName = "chemicals";
+        public string BodySolutionName = BloodstreamComponent.DefaultBloodSolutionName;
 
         /// <summary>
         ///     Time between reagents being ingested and them being
diff --git a/Content.Shared/Body/Events/MetabolismExclusionEvent.cs b/Content.Shared/Body/Events/MetabolismExclusionEvent.cs
new file mode 100644 (file)
index 0000000..791b8ed
--- /dev/null
@@ -0,0 +1,8 @@
+namespace Content.Shared.Body.Events;
+
+/// <summary>
+/// Event called by <see cref="Content.Server.Body.Systems.MetabolizerSystem"/> to get a list of
+/// blood like reagents for metabolism to skip.
+/// </summary>
+[ByRefEvent]
+public readonly record struct MetabolismExclusionEvent(List<string> ReagentList);
\ No newline at end of file
index e108cbfb276172a6a6ed47faebb0c9483f4a0b10..7c8fca6deb5a7420a8e3288c736b99453bb6203c 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Shared.Alert;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Events;
@@ -53,6 +54,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
         SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
         SubscribeLocalEvent<BloodstreamComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
         SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
+        SubscribeLocalEvent<BloodstreamComponent, MetabolismExclusionEvent>(OnMetabolismExclusion);
     }
 
     public override void Update(float frameTime)
@@ -72,11 +74,8 @@ public abstract class SharedBloodstreamSystem : EntitySystem
             if (!SolutionContainer.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
                 continue;
 
-            // Adds blood to their blood level if it is below the maximum; Blood regeneration. Must be alive.
-            if (bloodSolution.Volume < bloodSolution.MaxVolume && !_mobStateSystem.IsDead(uid))
-            {
-                TryModifyBloodLevel((uid, bloodstream), bloodstream.BloodRefreshAmount);
-            }
+            // Blood level regulation. Must be alive.
+            TryRegulateBloodLevel(uid, bloodstream.BloodRefreshAmount);
 
             // Removes blood from the bloodstream based on bleed amount (bleed rate)
             // as well as stop their bleeding to a certain extent.
@@ -86,14 +85,14 @@ public abstract class SharedBloodstreamSystem : EntitySystem
                 RaiseLocalEvent(uid, ref ev);
 
                 // Blood is removed from the bloodstream at a 1-1 rate with the bleed amount
-                TryModifyBloodLevel((uid, bloodstream), -ev.BleedAmount);
+                TryBleedOut((uid, bloodstream), ev.BleedAmount);
 
                 // Bleed rate is reduced by the bleed reduction amount in the bloodstream component.
                 TryModifyBleedAmount((uid, bloodstream), -ev.BleedReductionAmount);
             }
 
             // deal bloodloss damage if their blood level is below a threshold.
-            var bloodPercentage = GetBloodLevelPercentage((uid, bloodstream));
+            var bloodPercentage = GetBloodLevel(uid);
             if (bloodPercentage < bloodstream.BloodlossThreshold && !_mobStateSystem.IsDead(uid))
             {
                 // bloodloss damage is based on the base value, and modified by how low your blood level is.
@@ -133,9 +132,6 @@ public abstract class SharedBloodstreamSystem : EntitySystem
         if (args.Entity == entity.Comp.BloodSolution?.Owner)
             entity.Comp.BloodSolution = null;
 
-        if (args.Entity == entity.Comp.ChemicalSolution?.Owner)
-            entity.Comp.ChemicalSolution = null;
-
         if (args.Entity == entity.Comp.TemporarySolution?.Owner)
             entity.Comp.TemporarySolution = null;
     }
@@ -170,7 +166,6 @@ public abstract class SharedBloodstreamSystem : EntitySystem
     private void OnReactionAttempt(Entity<BloodstreamComponent> ent, ref SolutionRelayEvent<ReactionAttemptEvent> args)
     {
         if (args.Name != ent.Comp.BloodSolutionName
-            && args.Name != ent.Comp.ChemicalSolutionName
             && args.Name != ent.Comp.BloodTemporarySolutionName)
         {
             return;
@@ -221,7 +216,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
         var prob = Math.Clamp(totalFloat / 25, 0, 1);
         if (totalFloat > 0 && rand.Prob(prob))
         {
-            TryModifyBloodLevel(ent.AsNullable(), -total / 5);
+            TryBleedOut(ent.AsNullable(), total / 5);
             _audio.PlayPredicted(ent.Comp.InstantBloodSound, ent, args.Origin);
         }
 
@@ -269,7 +264,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
         }
 
         // If the mob's blood level is below the damage threshhold, the pale message is added.
-        if (GetBloodLevelPercentage(ent.AsNullable()) < ent.Comp.BloodlossThreshold)
+        if (GetBloodLevel(ent.AsNullable()) < ent.Comp.BloodlossThreshold)
         {
             args.Message.PushNewline();
             args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-looks-pale", ("target", ent.Owner)));
@@ -291,25 +286,46 @@ public abstract class SharedBloodstreamSystem : EntitySystem
     {
         TryModifyBleedAmount(ent.AsNullable(), -ent.Comp.BleedAmount);
 
-        if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
-            TryModifyBloodLevel(ent.AsNullable(), bloodSolution.AvailableVolume);
+        if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
+        {
+            SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
+            TryModifyBloodLevel(ent.AsNullable(), ent.Comp.BloodReferenceSolution.Volume);
+        }
+    }
 
-        if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution))
-            SolutionContainer.RemoveAllSolution(ent.Comp.ChemicalSolution.Value);
+    private void OnMetabolismExclusion(Entity<BloodstreamComponent> ent, ref MetabolismExclusionEvent args)
+    {
+        // Adding all blood reagents for filtering blood in metabolizer
+        foreach (var (reagentId, _) in ent.Comp.BloodReferenceSolution)
+        {
+            args.ReagentList.Add(reagentId.Prototype);
+        }
     }
 
     /// <summary>
-    /// Returns the current blood level as a percentage (between 0 and 1).
+    /// This returns the minimum amount of *usable* blood.
+    /// For multi reagent bloodstreams, if you have 100 of Reagent Y need 100, and 50 of Reagent X and need 100,
+    /// this will return 0.5f
     /// </summary>
-    public float GetBloodLevelPercentage(Entity<BloodstreamComponent?> ent)
+    /// <returns>Returns the current blood level as a value from 0 to <see cref="BloodstreamComponent.MaxVolumeModifier"/></returns>
+    public float GetBloodLevel(Entity<BloodstreamComponent?> entity)
     {
-        if (!Resolve(ent, ref ent.Comp)
-            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
+        if (!Resolve(entity, ref entity.Comp)
+            || !SolutionContainer.ResolveSolution(entity.Owner, entity.Comp.BloodSolutionName, ref entity.Comp.BloodSolution, out var bloodSolution)
+            || entity.Comp.BloodReferenceSolution.Volume == 0)
         {
             return 0.0f;
         }
 
-        return bloodSolution.FillFraction;
+        var totalBloodLevel = FixedPoint2.New(entity.Comp.MaxVolumeModifier); // Can't go above max volume factor...
+
+        foreach (var (reagentId, quantity) in entity.Comp.BloodReferenceSolution.Contents)
+        {
+            // Ideally we use a different calculation for blood pressure, this just defines how much *usable* blood you have!
+            totalBloodLevel = FixedPoint2.Min(totalBloodLevel, bloodSolution.GetTotalPrototypeQuantity(reagentId.Prototype) / quantity);
+        }
+
+        return (float)totalBloodLevel;
     }
 
     /// <summary>
@@ -327,33 +343,95 @@ public abstract class SharedBloodstreamSystem : EntitySystem
     /// <summary>
     /// Attempt to transfer a provided solution to internal solution.
     /// </summary>
-    public bool TryAddToChemicals(Entity<BloodstreamComponent?> ent, Solution solution)
+    public bool TryAddToBloodstream(Entity<BloodstreamComponent?> ent, Solution solution)
     {
         if (!Resolve(ent, ref ent.Comp, logMissing: false)
-            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution))
+            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution))
             return false;
 
-        if (SolutionContainer.TryAddSolution(ent.Comp.ChemicalSolution.Value, solution))
+        if (SolutionContainer.TryAddSolution(ent.Comp.BloodSolution.Value, solution))
             return true;
 
         return false;
     }
 
     /// <summary>
-    /// Removes a certain amount of all reagents except of a single excluded one from the bloodstream.
+    /// Removes a certain amount of all reagents except of a single excluded one from the bloodstream and blood itself.
     /// </summary>
-    public bool FlushChemicals(Entity<BloodstreamComponent?> ent, ProtoId<ReagentPrototype>? excludedReagentID, FixedPoint2 quantity)
+    /// <returns>
+    /// Solution of removed chemicals or null if none were removed.
+    /// </returns>
+    public Solution? FlushChemicals(Entity<BloodstreamComponent?> ent, FixedPoint2 quantity, ProtoId<ReagentPrototype>? excludedReagent = null )
     {
         if (!Resolve(ent, ref ent.Comp, logMissing: false)
-            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution, out var chemSolution))
+            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
+            return null;
+
+        var flushedSolution = new Solution();
+
+        for (var i = bloodSolution.Contents.Count - 1; i >= 0; i--)
+        {
+            var (reagentId, _) = bloodSolution.Contents[i];
+            if (ent.Comp.BloodReferenceSolution.ContainsPrototype(reagentId.Prototype) || reagentId.Prototype == excludedReagent)
+                continue;
+
+            var reagentFlushAmount = SolutionContainer.RemoveReagent(ent.Comp.BloodSolution.Value, reagentId, quantity);
+            flushedSolution.AddReagent(reagentId, reagentFlushAmount);
+        }
+
+        return flushedSolution.Volume == 0 ? null : flushedSolution;
+    }
+
+    /// <summary>
+    /// A simple helper that tries to move blood volume up or down by a specified amount.
+    /// Blood will not go over normal volume for this entity's bloodstream.
+    /// </summary>
+    public bool TryModifyBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount)
+    {
+        var reference = 1f;
+
+        if (amount < 0)
+        {
+            reference = 0f;
+            amount *= -1;
+        }
+
+        return TryRegulateBloodLevel(ent, amount, reference);
+    }
+
+    /// <summary>
+    /// Attempts to bring an entity's blood level to a modified equilibrium volume.
+    /// </summary>
+    /// <param name="ent">Entity whose bloodstream we're modifying.</param>
+    /// <param name="amount">The absolute maximum amount of blood we can add or remove.</param>
+    /// <param name="referenceFactor">The modifier for an entity's blood equilibrium, try to hit an entity's default blood volume multiplied by this value.</param>
+    /// <remarks>This CANNOT go above maximum blood volume!</remarks>
+    /// <returns>False if we were unable to regulate blood level. This may return true even if blood level doesn't change!</returns>
+    public bool TryRegulateBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount, float referenceFactor = 1f)
+    {
+        if (!Resolve(ent, ref ent.Comp, logMissing: false)
+            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution)
+            || amount == 0)
             return false;
 
-        for (var i = chemSolution.Contents.Count - 1; i >= 0; i--)
+        referenceFactor = Math.Clamp(referenceFactor, 0f, ent.Comp.MaxVolumeModifier);
+
+        foreach (var (referenceReagent, referenceQuantity) in ent.Comp.BloodReferenceSolution)
         {
-            var (reagentId, _) = chemSolution.Contents[i];
-            if (reagentId.Prototype != excludedReagentID)
+            var error = referenceQuantity * referenceFactor - bloodSolution.GetTotalPrototypeQuantity(referenceReagent.Prototype);
+            var adjustedAmount = amount * referenceQuantity / ent.Comp.BloodReferenceSolution.Volume;
+
+            if (error > 0)
             {
-                SolutionContainer.RemoveReagent(ent.Comp.ChemicalSolution.Value, reagentId, quantity);
+                error = FixedPoint2.Min(error, adjustedAmount);
+                var reagentToAdd = new ReagentId(referenceReagent.Prototype, GetEntityBloodData(ent));
+                bloodSolution.AddReagent(reagentToAdd, error);
+            }
+            else if (error < 0)
+            {
+                // invert the error since we're removing reagents...
+                error = FixedPoint2.Min( -error, adjustedAmount);
+                bloodSolution.RemoveReagent(referenceReagent, error);
             }
         }
 
@@ -361,43 +439,26 @@ public abstract class SharedBloodstreamSystem : EntitySystem
     }
 
     /// <summary>
-    ///  Attempts to modify the blood level of this entity directly.
+    /// Removes blood by spilling out the bloodstream.
     /// </summary>
-    public bool TryModifyBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount)
+    public bool TryBleedOut(Entity<BloodstreamComponent?> ent, FixedPoint2 amount)
     {
         if (!Resolve(ent, ref ent.Comp, logMissing: false)
-            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
-            return false;
-
-        if (amount >= 0)
+            || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution)
+            || amount <= 0)
         {
-            var min = FixedPoint2.Min(bloodSolution.AvailableVolume, amount);
-            var solution = ent.Comp.BloodReagents.Clone();
-            solution.ScaleTo(min);
-            solution.SetReagentData(GetEntityBloodData(ent));
-            SolutionContainer.AddSolution(ent.Comp.BloodSolution.Value, solution);
-            return min == amount;
+            return false;
         }
 
-        // Removal is more involved,
-        // since we also wanna handle moving it to the temporary solution
-        // and then spilling it if necessary.
-        var newSol = SolutionContainer.SplitSolution(ent.Comp.BloodSolution.Value, -amount);
+        var leakedBlood = SolutionContainer.SplitSolution(ent.Comp.BloodSolution.Value, amount);
 
         if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
             return true;
 
-        tempSolution.AddSolution(newSol, PrototypeManager);
+        tempSolution.AddSolution(leakedBlood, PrototypeManager);
 
         if (tempSolution.Volume > ent.Comp.BleedPuddleThreshold)
         {
-            // Pass some of the chemstream into the spilled blood.
-            if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution))
-            {
-                var temp = SolutionContainer.SplitSolution(ent.Comp.ChemicalSolution.Value, tempSolution.Volume / 10);
-                tempSolution.AddSolution(temp, PrototypeManager);
-            }
-
             _puddle.TrySpillAt(ent.Owner, tempSolution, out _, sound: false);
 
             tempSolution.RemoveAllSolution();
@@ -450,13 +511,6 @@ public abstract class SharedBloodstreamSystem : EntitySystem
             SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
         }
 
-        if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution, out var chemSolution))
-        {
-            tempSol.MaxVolume += chemSolution.MaxVolume;
-            tempSol.AddSolution(chemSolution, PrototypeManager);
-            SolutionContainer.RemoveAllSolution(ent.Comp.ChemicalSolution.Value);
-        }
-
         if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
         {
             tempSol.MaxVolume += tempSolution.MaxVolume;
@@ -488,24 +542,24 @@ public abstract class SharedBloodstreamSystem : EntitySystem
 
         if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
         {
-            ent.Comp.BloodReagents = reagents.Clone();
-            DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReagents));
+            ent.Comp.BloodReferenceSolution = reagents.Clone();
+            DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReferenceSolution));
             return;
         }
 
         var currentVolume = FixedPoint2.Zero;
-        foreach (var reagent in ent.Comp.BloodReagents)
+        foreach (var reagent in ent.Comp.BloodReferenceSolution)
         {
             currentVolume += bloodSolution.RemoveReagent(reagent.Reagent, quantity: bloodSolution.Volume, ignoreReagentData: true);
         }
 
-        ent.Comp.BloodReagents = reagents.Clone();
-        DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReagents));
+        ent.Comp.BloodReferenceSolution = reagents.Clone();
+        DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReferenceSolution));
 
         if (currentVolume == FixedPoint2.Zero)
             return;
 
-        var solution = ent.Comp.BloodReagents.Clone();
+        var solution = ent.Comp.BloodReferenceSolution.Clone();
         solution.ScaleSolution(currentVolume / solution.Volume);
         solution.SetReagentData(GetEntityBloodData(ent));
         SolutionContainer.AddSolution(ent.Comp.BloodSolution.Value, solution);
index a39f8514577895a063ea5ca32d8a901eb2094f49..e20045fc7828260e5468d3a1bd877e315a96d367 100644 (file)
@@ -335,8 +335,8 @@ public abstract class SharedInjectorSystem : EntitySystem
         EntityUid user)
     {
         // Get transfer amount. May be smaller than _transferAmount if not enough room
-        if (!SolutionContainer.ResolveSolution(target.Owner, target.Comp.ChemicalSolutionName,
-                ref target.Comp.ChemicalSolution, out var chemSolution))
+        if (!SolutionContainer.ResolveSolution(target.Owner, target.Comp.BloodSolutionName,
+                ref target.Comp.BloodSolution, out var bloodSolution))
         {
             LocId msg = target.Owner == user ? "injector-component-cannot-inject-message-self" : "injector-component-cannot-inject-message";
             _popup.PopupClient(
@@ -346,7 +346,7 @@ public abstract class SharedInjectorSystem : EntitySystem
             return false;
         }
 
-        var realTransferAmount = FixedPoint2.Min(injector.Comp.CurrentTransferAmount, chemSolution.AvailableVolume);
+        var realTransferAmount = FixedPoint2.Min(injector.Comp.CurrentTransferAmount, bloodSolution.AvailableVolume);
         if (realTransferAmount <= 0)
         {
             LocId msg = target.Owner == user ? "injector-component-cannot-inject-message-self" : "injector-component-cannot-inject-message";
@@ -358,9 +358,9 @@ public abstract class SharedInjectorSystem : EntitySystem
         }
 
         // Move units from attackSolution to targetSolution
-        var removedSolution = SolutionContainer.SplitSolution(target.Comp.ChemicalSolution.Value, realTransferAmount);
+        var removedSolution = SolutionContainer.SplitSolution(target.Comp.BloodSolution.Value, realTransferAmount);
 
-        _blood.TryAddToChemicals(target.AsNullable(), removedSolution);
+        _blood.TryAddToBloodstream(target.AsNullable(), removedSolution);
 
         _reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection);
 
@@ -438,20 +438,10 @@ public abstract class SharedInjectorSystem : EntitySystem
     private void DrawFromBlood(Entity<InjectorComponent> injector, Entity<BloodstreamComponent> target,
         Entity<SolutionComponent> injectorSolution, FixedPoint2 transferAmount, EntityUid user)
     {
-        var drawAmount = (float)transferAmount;
-
-        if (SolutionContainer.ResolveSolution(target.Owner, target.Comp.ChemicalSolutionName,
-                ref target.Comp.ChemicalSolution))
-        {
-            var chemTemp = SolutionContainer.SplitSolution(target.Comp.ChemicalSolution.Value, drawAmount * 0.15f);
-            SolutionContainer.TryAddSolution(injectorSolution, chemTemp);
-            drawAmount -= (float)chemTemp.Volume;
-        }
-
         if (SolutionContainer.ResolveSolution(target.Owner, target.Comp.BloodSolutionName,
                 ref target.Comp.BloodSolution))
         {
-            var bloodTemp = SolutionContainer.SplitSolution(target.Comp.BloodSolution.Value, drawAmount);
+            var bloodTemp = SolutionContainer.SplitSolution(target.Comp.BloodSolution.Value, transferAmount);
             SolutionContainer.TryAddSolution(injectorSolution, bloodTemp);
         }
 
index 71b1bd6396f23196c7a5cedf66f30997d3c07572..b0aca4f10d90c02c40b8e6cd9b8337661ea303b4 100644 (file)
@@ -108,7 +108,7 @@ public sealed class DevourSystem : EntitySystem
         // Grant ichor if the devoured thing meets the dragon's food preference
         if (args.Args.Target != null && _whitelistSystem.IsWhitelistPassOrNull(ent.Comp.FoodPreferenceWhitelist, (EntityUid)args.Args.Target))
         {
-            _bloodstreamSystem.TryAddToChemicals(ent.Owner, ichorInjection);
+            _bloodstreamSystem.TryAddToBloodstream(ent.Owner, ichorInjection);
         }
 
         // If the devoured thing meets the stomach whitelist criteria, add it to the stomach
index 402a50538a6759963cbc4efa1d18a88a266da07e..a5752b858e52bf10fac21f15f26b60c8efc5f552 100644 (file)
@@ -19,7 +19,7 @@ public sealed partial class CleanBloodstreamEntityEffectSystem : EntityEffectSys
     {
         var scale = args.Scale * args.Effect.CleanseRate;
 
-        _bloodstream.FlushChemicals((entity, entity), args.Effect.Excluded, scale);
+        _bloodstream.FlushChemicals((entity, entity), scale, args.Effect.Excluded);
     }
 }
 
index 3fcc3da8482dc168102fcc1429c91b3ff5431b82..a7bbe39c56ff11754a73368ecee9a3689bf843d2 100644 (file)
@@ -108,7 +108,7 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
                 && _bloodstreamQuery.TryComp(patient, out var bloodstream))
             {
                 var solutionToInject = _solutionContainer.SplitSolution(containerSolution.Value, cryoPod.BeakerTransferAmount);
-                _bloodstream.TryAddToChemicals((patient.Value, bloodstream), solutionToInject);
+                _bloodstream.TryAddToBloodstream((patient.Value, bloodstream), solutionToInject);
                 _reactive.DoEntityReaction(patient.Value, solutionToInject, ReactionMethod.Injection);
             }
         }
index bd9f0003ba815e47355af7a9b8ad00613c419666..fa27b4688e486d01de34348f6548c27ca35d8f0f 100644 (file)
@@ -141,7 +141,7 @@ public sealed class HealingSystem : EntitySystem
             // Is ent missing blood that we can restore?
             if (healing.Comp.ModifyBloodLevel > 0
                 && _solutionContainerSystem.ResolveSolution(target.Owner, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)
-                && bloodSolution.Volume < bloodSolution.MaxVolume)
+                && _bloodstreamSystem.GetBloodLevel((target, bloodstream)) < 1)
             {
                 return true;
             }
index d398faef20727d19719190cef06454c17b42b1ee..cf927754e323bcc3e8f49e263685de2f990c707b 100644 (file)
@@ -107,14 +107,17 @@ public sealed class VomitSystem : EntitySystem
         {
             var vomitAmount = solutionSize;
 
-            // Takes 10% of the chemicals removed from the chem stream
-            if (_solutionContainer.ResolveSolution(uid, bloodStream.ChemicalSolutionName, ref bloodStream.ChemicalSolution))
+            // Flushes small portion of the chemicals removed from the bloodstream stream
+            if (_solutionContainer.ResolveSolution(uid, bloodStream.BloodSolutionName, ref bloodStream.BloodSolution))
             {
-                var vomitChemstreamAmount = _solutionContainer.SplitSolution(bloodStream.ChemicalSolution.Value, vomitAmount);
-                vomitChemstreamAmount.ScaleSolution(ChemMultiplier);
-                solution.AddSolution(vomitChemstreamAmount, _proto);
-
-                vomitAmount -= (float)vomitChemstreamAmount.Volume;
+                var vomitChemstreamAmount = _bloodstream.FlushChemicals((uid, bloodStream), vomitAmount);
+
+                if (vomitChemstreamAmount != null)
+                {
+                    vomitChemstreamAmount.ScaleSolution(ChemMultiplier);
+                    solution.AddSolution(vomitChemstreamAmount, _proto);
+                    vomitAmount -= (float)vomitChemstreamAmount.Volume;
+                }
             }
 
             // Makes a vomit solution the size of 90% of the chemicals removed from the chemstream
index edb04f959e65e667b636daefa4242e03c07c4c45..1d718dbdf337a70be36b3b65b170761285d7798f 100644 (file)
@@ -102,17 +102,17 @@ public sealed class RootableSystem : EntitySystem
     /// </summary>
     private void ReactWithEntity(Entity<RootableComponent, BloodstreamComponent> ent, Entity<PuddleComponent> puddleEntity, Solution solution)
     {
-        if (!_solutionContainer.ResolveSolution(ent.Owner, ent.Comp2.ChemicalSolutionName, ref ent.Comp2.ChemicalSolution, out var chemSolution) || chemSolution.AvailableVolume <= 0)
+        if (!_solutionContainer.ResolveSolution(ent.Owner, ent.Comp2.BloodSolutionName, ref ent.Comp2.BloodSolution, out var bloodSolution) || bloodSolution.AvailableVolume <= 0)
             return;
 
         var availableTransfer = FixedPoint2.Min(solution.Volume, ent.Comp1.TransferRate);
-        var transferAmount = FixedPoint2.Min(availableTransfer, chemSolution.AvailableVolume);
+        var transferAmount = FixedPoint2.Min(availableTransfer, bloodSolution.AvailableVolume);
         var transferSolution = _solutionContainer.SplitSolution(puddleEntity.Comp.Solution!.Value, transferAmount);
 
         _reactive.DoEntityReaction(ent, transferSolution, ReactionMethod.Ingestion);
 
         // Log solution addition by puddle.
-        if (_blood.TryAddToChemicals((ent, ent.Comp2), transferSolution))
+        if (_blood.TryAddToBloodstream((ent, ent.Comp2), transferSolution))
             _logger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(ent):target} absorbed puddle {SharedSolutionContainerSystem.ToPrettyString(transferSolution)}");
     }
 
index 9965167256572ab9cf3674df1c1461c23fe204b7..05a72b3b337fec44bab29eb2394a92d6bc2e05e4 100644 (file)
   - type: SentienceTarget
     flavorKind: station-event-random-sentience-flavor-organic
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
   - type: ReplacementAccent
     accent: mouse
   - type: MeleeWeapon
     - Bee
     - Trash
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: InsectBlood
-        Quantity: 1
-    bloodMaxVolume: 0.1
+        Quantity: 0.1
   - type: MobPrice
     price: 50
   - type: NPCRetaliation
     factions:
     - SimpleHostile
   - type: Bloodstream
-    bloodMaxVolume: 0.1
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 0.1
   - type: ZombieImmune
 
 
     interactSuccessSound:
       path: /Audio/Animals/chicken_cluck_happy.ogg
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: EggLayer
     eggSpawn:
     - id: FoodEgg
       Dead:
         Base: cockroach_dead
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: InsectBlood
-        Quantity: 1
-    bloodMaxVolume: 20
+        Quantity: 20
   - type: Edible
   - type: FlavorProfile
     flavors:
     damageContainer: Biological
     damageModifierSet: Moth
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: InsectBlood
-        Quantity: 1
+        Quantity: 20
   - type: Respirator
     damage:
       types:
     interactSuccessSound:
       path: /Audio/Animals/duck_quack_happy.ogg
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: EggLayer
     eggSpawn:
     - id: FoodEgg
     tags:
     - Trash
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: InsectBlood
-        Quantity: 1
-    bloodMaxVolume: 0.1
+        Quantity: 0.1
   - type: MobPrice
     price: 50
   - type: Destructible
   - type: ReplacementAccent
     accent: crab
   - type: Bloodstream
-    bloodMaxVolume: 50
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: CopperBlood
-        Quantity: 1
+        Quantity: 50
   - type: Tag
     tags:
     - VimPilot
     interactSuccessSound:
       path: /Audio/Animals/goose_honk.ogg
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: NpcFactionMember
     factions:
     - Passive
     - id: FoodMeat
       amount: 4
   - type: Bloodstream
-    bloodMaxVolume: 300
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 300
     # if you fuck with the gorilla he will harambe you
   - type: MeleeWeapon
     soundHit:
       types:
         Piercing: 0
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution: 
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
   - type: CanEscapeInventory
   - type: MobPrice
     price: 50
   - type: RadiationSource
     intensity: 0.3
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: UnstableMutagen
-        Quantity: 1
+        Quantity: 50
   - type: SolutionContainerManager
     solutions:
       food:
     interactSuccessSound:
       path: /Audio/Animals/lizard_happy.ogg
   - type: Bloodstream
-    bloodMaxVolume: 150
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 150
   - type: Damageable
     damageContainer: Biological
     damageModifierSet: Scale
     interactFailureString: petting-failure-generic
     interactSuccessSpawn: EffectHearts
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
 
 - type: entity
   name: frog
     interactSuccessSound:
       path: /Audio/Animals/frog_ribbit.ogg
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
   - type: Tag
     tags:
     - VimPilot
     interactSuccessSound:
       path: /Audio/Animals/parrot_raught.ogg
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
 
 - type: entity
   parent: MobParrotBase
     interactFailureString: petting-failure-generic
     interactSuccessSpawn: EffectHearts
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
   - type: Damageable
     damageContainer: Biological
     damageModifierSet: Scale
   - type: Spider
   - type: IgnoreSpiderWeb
   - type: Bloodstream
-    bloodMaxVolume: 150
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: CopperBlood
-        Quantity: 1
+        Quantity: 150
   - type: Speech
     speechVerb: Arachnid
     speechSounds: Arachnid
     - type: Speech
       speechVerb: Cluwne
     - type: Bloodstream
-      bloodMaxVolume: 150
-      bloodReagents:
+      bloodReferenceSolution:
         reagents:
         - ReagentId: Laughter
-          Quantity: 1
+          Quantity: 150
 - type: entity
   name: wizard spider
   parent: MobGiantSpider
     attributes:
       gender: epicene
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: MeleeWeapon
     angle: 0
     animation: WeaponArcBite
     attributes:
       gender: epicene
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: DemonsBlood
-        Quantity: 1
+        Quantity: 150
   - type: Damageable
     damageContainer: BiologicalMetaphysical
     damageModifierSet: Infernal
     interactSuccessSound:
       path: /Audio/Animals/fox_squeak.ogg
   - type: Bloodstream
-    bloodMaxVolume: 60
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 60
   - type: CanEscapeInventory
     baseResistTime: 3
   - type: MobPrice
     speciesId: cat
     templateId: pet
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Sap
-        Quantity: 1
-    bloodMaxVolume: 60
+        Quantity: 60
   - type: DamageStateVisuals
     states:
       Alive:
     - id: FoodMeat
       amount: 4
   - type: Bloodstream
-    bloodMaxVolume: 300
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 300
     # Horns though
   - type: MeleeWeapon
     damage:
index 4c9f851ba0c8d144d95244a4e5bc14389f9346a6..ddb7b5f68c27d5ab9531a1772c6fdb4cf4a49416 100644 (file)
   - type: ReplacementAccent
     accent: xeno
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: FerrochromicAcid
-        Quantity: 1
-    bloodMaxVolume: 75 #we don't want the map to become pools of blood
+        Quantity: 75 #we don't want the map to become pools of blood
     bloodlossDamage:
       types:
         Bloodloss:
index 5fbfb3eb9119616a3ae2b013ec08a3eb78e9b36d..f0a48a6577214d97615a58d3dbbc80cca0cb3537 100644 (file)
     baseWalkSpeed : 2.00
     baseSprintSpeed : 2.00
   - type: Bloodstream
-    bloodMaxVolume: 350
-    chemicalMaxVolume: 0
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 350
   - type: MobThresholds
     thresholds:
       0: Alive
     capacity: 1
     count: 1
   - type: Bloodstream
-    bloodMaxVolume: 200
-    chemicalMaxVolume: 0
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 200
   - type: NpcFactionMember
     factions:
     - SimpleHostile
index 23e32309377a68159a5697a0789c8e97bf5bc5b9..5c575da396b376e3c21d6464ac7f5a919af71c63 100644 (file)
     - type: Input
       context: "human"
     - type: Bloodstream
-      bloodMaxVolume: 300
-      bloodReagents:
+      bloodReferenceSolution:
         reagents:
         - ReagentId: Laughter
-          Quantity: 1
+          Quantity: 300
 - type: entity
   name: behonker
   parent: BaseMobBehonker
index 79df1005983f2b322c60ea2404b498cd8da1f836..b430689361d41addb0e9996ad5fc989e9d6c4fc8 100644 (file)
     speedModifierThresholds:
       50: 0.4
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Water
-        Quantity: 1
-    chemicalMaxVolume: 100
+        Quantity: 100
   - type: StatusEffects
     allowed:
     - Electrocution
   suffix: Beer
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Beer
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#cfa85f"
   - type: Sprite
   suffix: Pax
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Pax
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#AAAAAA"
   - type: Sprite
   suffix: Nocturine
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Nocturine
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#128e80"
   - type: Sprite
   suffix: THC
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: THC
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#808080"
   - type: Sprite
   suffix: Bicaridine
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Bicaridine
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#ffaa00"
   - type: Sprite
   suffix: Toxin
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Toxin
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#cf3600"
   - type: Sprite
   suffix: Napalm
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Napalm
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#FA00AF"
   - type: Sprite
   suffix: Omnizine
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Omnizine
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#fcf7f9"
   - type: Sprite
   suffix: Mute Toxin
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: MuteToxin
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#0f0f0f"
   - type: Sprite
   suffix: Norepinephric Acid
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: NorepinephricAcid
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#96a8b5"
   - type: Sprite
   suffix: Ephedrine
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Ephedrine
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#D2FFFA"
   - type: Sprite
   suffix: Robust Harvest
   components:
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: RobustHarvest
-        Quantity: 1
+        Quantity: 100
   - type: PointLight
     color: "#3e901c"
   - type: Sprite
index f3ec5ff80b3dc83a856fe1e4b26587c9d3037f1c..75f0aa83b8bf287eeb0c354135c9d1ee78fb7e4f 100644 (file)
     - id: FoodMeat
       amount: 1
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: CombatMode
   - type: MeleeWeapon
     soundHit:
     - id: FoodMeat
       amount: 1
   - type: Bloodstream
-    bloodMaxVolume: 100
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 100
   - type: CombatMode
   - type: MeleeWeapon
     soundHit:
index ad392145e1092393966df3671804e08ba3b32189..ac4f476a3568cc322939af18a2ddd8a380344be6 100644 (file)
       - map: [ "enum.DamageStateVisualLayers.Base" ]
         state: alive
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: JuiceTomato
-        Quantity: 1
-    bloodMaxVolume: 50
-    chemicalMaxVolume: 30
+        Quantity: 50
   - type: DamageStateVisuals
     states:
       Alive:
index f9e8e4e33aa15ab428084acc6d083983bfc433e7..d0ea13f26ed8299d99fdd1fd01dd21262a12be77 100644 (file)
     - RadiationProtection
     - Adrenaline
   - type: Bloodstream
-    bloodMaxVolume: 150
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 150
   - type: MobPrice
     price: 150
   - type: FloatingVisuals
index 74c6bb1468df4dfe53887f73c3977319bed0af21..80269feef37499a27624b508adfd00955279edb2 100644 (file)
     damageContainer: Biological
     damageModifierSet: Slime
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Slime
-        Quantity: 1
+        Quantity: 150
     bloodlossDamage:
       types:
         Bloodloss:
index 8acc571ccf5d1fb25ec68f269829a9fdeca051b6..d052728379c09e2d57e496ba0f4930b972365cc4 100644 (file)
     critThreshold: 150
   - type: MovementAlwaysTouching
   - type: Bloodstream
-    bloodMaxVolume: 300
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Cryoxadone
-        Quantity: 1
+        Quantity: 300
   - type: CombatMode
   - type: Temperature
     heatDamageThreshold: 500
       amount: 1
       prob: 0.5
   - type: Bloodstream
-    bloodMaxVolume: 250
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Cryoxadone
-        Quantity: 1
+        Quantity: 250
   - type: Fixtures
     fixtures:
       fix1:
           amount: 1
           prob: 0.3
     - type: Bloodstream
-      bloodMaxVolume: 200
-      bloodReagents:
+      bloodReferenceSolution:
         reagents:
         - ReagentId: Cryoxadone
-          Quantity: 1
+          Quantity: 200
     - type: Fixtures
       fixtures:
         fix1:
   - type: CombatMode
     combatToggleAction: ActionCombatModeToggleOff
   - type: Bloodstream
-    bloodMaxVolume: 30
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Cryoxadone
-        Quantity: 1
+        Quantity: 30
   - type: CanEscapeInventory
   - type: MobPrice
     price: 50
index be7c034a39ea0f7f7afa78e96a876aee0f569463..780c4b0330b8ddd76e4cc5eafd8049b14ba531b2 100644 (file)
     - id: FoodMeatXeno
       amount: 1
   - type: Bloodstream
-    bloodMaxVolume: 50
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 50
   - type: CombatMode
   - type: MeleeWeapon
     soundHit:
index f1c860364bced37ce4b444de68b66800120f49fb..247d7636e59d78b0312c9ce1f1a84bc20cf74267 100644 (file)
   - type: Stamina
     critThreshold: 200
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: FluorosulfuricAcid
-        Quantity: 1
-    bloodMaxVolume: 650
+        Quantity: 650
   - type: MeleeWeapon
     altDisarm: false
     angle: 0
   - type: Stamina
     critThreshold: 200
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: FluorosulfuricAcid
-        Quantity: 1
-    bloodMaxVolume: 300
+        Quantity: 300
   - type: MeleeWeapon
     altDisarm: false
     angle: 0
index f4e6f1d39cd401d434fdf87a961d050c6a83a235..07bff11974f622771e2d73fec8cce2a496ab2169 100644 (file)
@@ -7,7 +7,10 @@
   abstract: true
   components:
   - type: Bloodstream
-    bloodMaxVolume: 650
+    bloodReferenceSolution:
+      reagents:
+      - ReagentId: Blood
+        Quantity: 650
   - type: GhostRole
     allowMovement: true
     allowSpeech: true
index e38e93a9300c3e6d436ae1f1d75b1456806785a2..43c87f899487df4633f4cd981e7de4a9a37fdf06 100644 (file)
       - !type:WashCreamPie
   # Damage (Self)
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: CopperBlood
-        Quantity: 1
+        Quantity: 300
   # Damage (Others)
   - type: MeleeWeapon
     animation: WeaponArcBite
index 4dafbf99b90edc5eafdfcedd474b9dc8af3018fa..1e9d7ea69680c53ce6d522bb5ce1775594cd658f 100644 (file)
       - id: FoodMeatPlant
         amount: 5
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Sap
-        Quantity: 1
+        Quantity: 300
   - type: Reactive
     groups:
       Flammable: [ Touch ]
index 7844c8008274f360c83fcef96dcd752a7c21afd1..794317464d3256c918ab44fbf5af8934d36080e9 100644 (file)
       - id: FoodBakedCookie #should be replaced with gingerbread sheets or something... provided you're willing to make a full spriteset of those.
         amount: 5
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: Sugar
-        Quantity: 1
+        Quantity: 100
       - ReagentId: Butter
-        Quantity: 2
+        Quantity: 200
   - type: Fixtures
     fixtures:
       fix1:
index d0adbcc28f8b3a8fe2f597f240847e1c8b4e65c1..83031d61d94bbecdf007ddb5f6267165e474f422 100644 (file)
     - id: FoodMeat
       amount: 5
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: InsectBlood
-        Quantity: 1
+        Quantity: 300
   - type: DamageVisuals
     damageOverlayGroups:
       Brute:
index 89115dd1c27de9d206c8d9c92fc0063695a15aff..6b777f8c3be76dfdf529836acb147122bd54b855 100644 (file)
       Burn:
         sprite: Mobs/Effects/burn_damage.rsi
   - type: Bloodstream
-    bloodReagents: # TODO Color slime blood based on their slime color or smth
+    bloodReferenceSolution: # TODO Color slime blood based on their slime color or smth
       reagents:
       - ReagentId: Slime
-        Quantity: 1
+        Quantity: 300
   - type: Barotrauma
     damage:
       types:
index ecc0cea5cd2672407620cff965ab3950b212dc04..c0b70ae2b8f8e965ecdeffbd05aa255098ebda96 100644 (file)
       Burn:
         sprite: Mobs/Effects/burn_damage.rsi
   - type: Bloodstream
-    bloodReagents:
+    bloodReferenceSolution:
       reagents:
       - ReagentId: AmmoniaBlood
-        Quantity: 1
+        Quantity: 300
   - type: MeleeWeapon
     soundHit:
       collection: AlienClaw
index e2e918152a584c55ebbd722dc7ae9270545b2de6..a21ca14c5db3bc5e9891c40e7285e162fb6e9b6d 100644 (file)
   components:
   - type: SolutionContainerManager
   - type: InjectableSolution
-    solution: chemicals
+    solution: bloodstream
   - type: Bloodstream
     bloodlossDamage:
       types:
index c048b27b1a6d6264ef4c5c10994096486a56b843..aa0cf4b6cda4f2d62d3d60bd5c69989230f832ae 100644 (file)
 
 - type: reaction
   id: RedMead
+  requiredMixerCategories:
+  - Stir
   reactants:
     Mead:
       amount: 1
index d27cdefc95d6e196d8606beea9724fc0c81c1b91..5a3764a504d7403fd764f1f1ee5b9bb90dee4e08 100644 (file)
@@ -43,6 +43,8 @@
 
 - type: reaction
   id: SpaceGlue
+  requiredMixerCategories:
+  - Stir # prevents turning slimes to glue, TODO: make it react only X units per second; make bloodstream an open thermal system
   minTemp: 370
   reactants:
     SpaceLube:
index d5c0efd21aa0bc310c82c1b628fbe89f0f2d797e..64fcfdf92079e916152d17625b1b261d3bbee6ae 100644 (file)
@@ -22,6 +22,8 @@
 
 - type: reaction
   id: SapBoiling
+  requiredMixerCategories:
+  - Stir # prevents cooking diona from inside out, TODO: make it react only X units per second; make bloodstream an open thermal system
   impact: Low
   minTemp: 377
   reactants: