From: Nikovnik <116634167+nkokic@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:21:16 +0000 (+0100) Subject: Metabolizing bloodstream (#35071) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=0e76d4e5ed6ae70a31d60beb350b7ce3c1a2f9b7;p=space-station-14.git Metabolizing bloodstream (#35071) * 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> --- diff --git a/Content.Server/Body/Components/MetabolizerComponent.cs b/Content.Server/Body/Components/MetabolizerComponent.cs index 46d2fdd8e8..2401db5aac 100644 --- a/Content.Server/Body/Components/MetabolizerComponent.cs +++ b/Content.Server/Body/Components/MetabolizerComponent.cs @@ -42,7 +42,7 @@ namespace Content.Server.Body.Components /// From which solution will this metabolizer attempt to metabolize chemicals /// [DataField("solution")] - public string SolutionName = BloodstreamComponent.DefaultChemicalsSolutionName; + public string SolutionName = BloodstreamComponent.DefaultBloodSolutionName; /// /// Does this component use a solution on it's parent entity (the body) or itself diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index a58deec494..08f640711a 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -21,9 +21,6 @@ public sealed class BloodstreamSystem : SharedBloodstreamSystem private void OnComponentInit(Entity 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); } diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index b5b30ae74c..35c7b0572a 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -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(); + 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(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(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; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs index 7b4deea9f4..e6e1642295 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs @@ -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; } diff --git a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs index 7c9d02c561..c787132504 100644 --- a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs @@ -264,14 +264,14 @@ public sealed class SmokeSystem : EntitySystem if (!TryComp(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)}"); diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index bc6285c1d2..b7f48cade4 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -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; } diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs index 4960ff1ba8..ae35fa4825 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs @@ -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; diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index cfa22b2a14..b4aee77cef 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -193,7 +193,7 @@ public sealed partial class ZombieSystem zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor; zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers); - if (TryComp(target, out var stream) && stream.BloodReagents is { } reagents) + if (TryComp(target, out var stream) && stream.BloodReferenceSolution is { } reagents) zombiecomp.BeforeZombifiedBloodReagents = reagents.Clone(); _humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp); diff --git a/Content.Shared/Body/Components/BloodstreamComponent.cs b/Content.Shared/Body/Components/BloodstreamComponent.cs index 2ebfae6f33..8309397b62 100644 --- a/Content.Shared/Body/Components/BloodstreamComponent.cs +++ b/Content.Shared/Body/Components/BloodstreamComponent.cs @@ -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. /// - /// Max volume of internal chemical solution storage + /// Modifier applied to to determine maximum volume for bloodstream. /// [DataField] - public FixedPoint2 ChemicalMaxVolume = FixedPoint2.New(250); + public float MaxVolumeModifier = 2f; /// - /// Max volume of internal blood storage, - /// and starting level of blood. - /// - [DataField] - public FixedPoint2 BloodMaxVolume = FixedPoint2.New(300); - - /// - /// Which reagents are considered this entities 'blood'? + /// Defines which reagents are considered as 'blood' and how much of it is normal. /// /// /// Slime-people might use slime as their blood or something like that. /// [DataField, AutoNetworkedField] - public Solution BloodReagents = new([new("Blood", 1)]); + public Solution BloodReferenceSolution = new([new("Blood", 300)]); /// /// Name/Key that is indexed by. @@ -165,12 +157,6 @@ public sealed partial class BloodstreamComponent : Component [DataField] public string BloodSolutionName = DefaultBloodSolutionName; - /// - /// Name/Key that is indexed by. - /// - [DataField] - public string ChemicalSolutionName = DefaultChemicalsSolutionName; - /// /// Name/Key that is indexed by. /// @@ -183,12 +169,6 @@ public sealed partial class BloodstreamComponent : Component [ViewVariables] public Entity? BloodSolution; - /// - /// Internal solution for reagent storage - /// - [ViewVariables] - public Entity? ChemicalSolution; - /// /// Temporary blood solution. /// When blood is lost, it goes to this solution, and when this diff --git a/Content.Shared/Body/Components/StomachComponent.cs b/Content.Shared/Body/Components/StomachComponent.cs index ca20cc544a..e1413de9e4 100644 --- a/Content.Shared/Body/Components/StomachComponent.cs +++ b/Content.Shared/Body/Components/StomachComponent.cs @@ -45,7 +45,7 @@ namespace Content.Shared.Body.Components /// What solution should this stomach push reagents into, on the body? /// [DataField] - public string BodySolutionName = "chemicals"; + public string BodySolutionName = BloodstreamComponent.DefaultBloodSolutionName; /// /// 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 index 0000000000..791b8ed959 --- /dev/null +++ b/Content.Shared/Body/Events/MetabolismExclusionEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Body.Events; + +/// +/// Event called by to get a list of +/// blood like reagents for metabolism to skip. +/// +[ByRefEvent] +public readonly record struct MetabolismExclusionEvent(List ReagentList); \ No newline at end of file diff --git a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs index e108cbfb27..7c8fca6deb 100644 --- a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs +++ b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs @@ -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(OnBeingGibbed); SubscribeLocalEvent(OnApplyMetabolicMultiplier); SubscribeLocalEvent(OnRejuvenate); + SubscribeLocalEvent(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 ent, ref SolutionRelayEvent 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 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); + } } /// - /// 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 /// - public float GetBloodLevelPercentage(Entity ent) + /// Returns the current blood level as a value from 0 to + public float GetBloodLevel(Entity 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; } /// @@ -327,33 +343,95 @@ public abstract class SharedBloodstreamSystem : EntitySystem /// /// Attempt to transfer a provided solution to internal solution. /// - public bool TryAddToChemicals(Entity ent, Solution solution) + public bool TryAddToBloodstream(Entity 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; } /// - /// 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. /// - public bool FlushChemicals(Entity ent, ProtoId? excludedReagentID, FixedPoint2 quantity) + /// + /// Solution of removed chemicals or null if none were removed. + /// + public Solution? FlushChemicals(Entity ent, FixedPoint2 quantity, ProtoId? 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; + } + + /// + /// 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. + /// + public bool TryModifyBloodLevel(Entity ent, FixedPoint2 amount) + { + var reference = 1f; + + if (amount < 0) + { + reference = 0f; + amount *= -1; + } + + return TryRegulateBloodLevel(ent, amount, reference); + } + + /// + /// Attempts to bring an entity's blood level to a modified equilibrium volume. + /// + /// Entity whose bloodstream we're modifying. + /// The absolute maximum amount of blood we can add or remove. + /// The modifier for an entity's blood equilibrium, try to hit an entity's default blood volume multiplied by this value. + /// This CANNOT go above maximum blood volume! + /// False if we were unable to regulate blood level. This may return true even if blood level doesn't change! + public bool TryRegulateBloodLevel(Entity 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 } /// - /// Attempts to modify the blood level of this entity directly. + /// Removes blood by spilling out the bloodstream. /// - public bool TryModifyBloodLevel(Entity ent, FixedPoint2 amount) + public bool TryBleedOut(Entity 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); diff --git a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs index a39f851457..e20045fc78 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs @@ -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 injector, Entity target, Entity 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); } diff --git a/Content.Shared/Devour/DevourSystem.cs b/Content.Shared/Devour/DevourSystem.cs index 71b1bd6396..b0aca4f10d 100644 --- a/Content.Shared/Devour/DevourSystem.cs +++ b/Content.Shared/Devour/DevourSystem.cs @@ -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 diff --git a/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs index 402a50538a..a5752b858e 100644 --- a/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs +++ b/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs @@ -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); } } diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs index 3fcc3da848..a7bbe39c56 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -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); } } diff --git a/Content.Shared/Medical/Healing/HealingSystem.cs b/Content.Shared/Medical/Healing/HealingSystem.cs index bd9f0003ba..fa27b4688e 100644 --- a/Content.Shared/Medical/Healing/HealingSystem.cs +++ b/Content.Shared/Medical/Healing/HealingSystem.cs @@ -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; } diff --git a/Content.Shared/Medical/VomitSystem.cs b/Content.Shared/Medical/VomitSystem.cs index d398faef20..cf927754e3 100644 --- a/Content.Shared/Medical/VomitSystem.cs +++ b/Content.Shared/Medical/VomitSystem.cs @@ -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 diff --git a/Content.Shared/Rootable/RootableSystem.cs b/Content.Shared/Rootable/RootableSystem.cs index edb04f959e..1d718dbdf3 100644 --- a/Content.Shared/Rootable/RootableSystem.cs +++ b/Content.Shared/Rootable/RootableSystem.cs @@ -102,17 +102,17 @@ public sealed class RootableSystem : EntitySystem /// private void ReactWithEntity(Entity ent, Entity 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)}"); } diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 9965167256..05a72b3b33 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -50,7 +50,10 @@ - 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 @@ -140,11 +143,10 @@ - Bee - Trash - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 0.1 + Quantity: 0.1 - type: MobPrice price: 50 - type: NPCRetaliation @@ -190,7 +192,10 @@ factions: - SimpleHostile - type: Bloodstream - bloodMaxVolume: 0.1 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 0.1 - type: ZombieImmune @@ -264,7 +269,10 @@ interactSuccessSound: path: /Audio/Animals/chicken_cluck_happy.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: EggLayer eggSpawn: - id: FoodEgg @@ -391,11 +399,10 @@ Dead: Base: cockroach_dead - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 20 + Quantity: 20 - type: Edible - type: FlavorProfile flavors: @@ -558,10 +565,10 @@ damageContainer: Biological damageModifierSet: Moth - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 + Quantity: 20 - type: Respirator damage: types: @@ -713,7 +720,10 @@ interactSuccessSound: path: /Audio/Animals/duck_quack_happy.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: EggLayer eggSpawn: - id: FoodEgg @@ -841,11 +851,10 @@ tags: - Trash - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 0.1 + Quantity: 0.1 - type: MobPrice price: 50 - type: Destructible @@ -1001,11 +1010,10 @@ - type: ReplacementAccent accent: crab - type: Bloodstream - bloodMaxVolume: 50 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: CopperBlood - Quantity: 1 + Quantity: 50 - type: Tag tags: - VimPilot @@ -1162,7 +1170,10 @@ interactSuccessSound: path: /Audio/Animals/goose_honk.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: NpcFactionMember factions: - Passive @@ -1204,7 +1215,10 @@ - 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: @@ -1875,7 +1889,10 @@ types: Piercing: 0 - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: CanEscapeInventory - type: MobPrice price: 50 @@ -2011,10 +2028,10 @@ - type: RadiationSource intensity: 0.3 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: UnstableMutagen - Quantity: 1 + Quantity: 50 - type: SolutionContainerManager solutions: food: @@ -2080,7 +2097,10 @@ interactSuccessSound: path: /Audio/Animals/lizard_happy.ogg - type: Bloodstream - bloodMaxVolume: 150 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 150 - type: Damageable damageContainer: Biological damageModifierSet: Scale @@ -2134,7 +2154,10 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: entity name: frog @@ -2190,7 +2213,10 @@ interactSuccessSound: path: /Audio/Animals/frog_ribbit.ogg - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: Tag tags: - VimPilot @@ -2281,7 +2307,10 @@ interactSuccessSound: path: /Audio/Animals/parrot_raught.ogg - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: entity parent: MobParrotBase @@ -2469,7 +2498,10 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: Damageable damageContainer: Biological damageModifierSet: Scale @@ -2532,11 +2564,10 @@ - type: Spider - type: IgnoreSpiderWeb - type: Bloodstream - bloodMaxVolume: 150 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: CopperBlood - Quantity: 1 + Quantity: 150 - type: Speech speechVerb: Arachnid speechSounds: Arachnid @@ -2681,11 +2712,10 @@ - type: Speech speechVerb: Cluwne - type: Bloodstream - bloodMaxVolume: 150 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Laughter - Quantity: 1 + Quantity: 150 - type: entity name: wizard spider parent: MobGiantSpider @@ -2881,7 +2911,10 @@ attributes: gender: epicene - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: MeleeWeapon angle: 0 animation: WeaponArcBite @@ -3014,10 +3047,10 @@ attributes: gender: epicene - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: DemonsBlood - Quantity: 1 + Quantity: 150 - type: Damageable damageContainer: BiologicalMetaphysical damageModifierSet: Infernal @@ -3539,7 +3572,10 @@ interactSuccessSound: path: /Audio/Animals/fox_squeak.ogg - type: Bloodstream - bloodMaxVolume: 60 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 60 - type: CanEscapeInventory baseResistTime: 3 - type: MobPrice @@ -3657,11 +3693,10 @@ speciesId: cat templateId: pet - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Sap - Quantity: 1 - bloodMaxVolume: 60 + Quantity: 60 - type: DamageStateVisuals states: Alive: @@ -3765,7 +3800,10 @@ - id: FoodMeat amount: 4 - type: Bloodstream - bloodMaxVolume: 300 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 300 # Horns though - type: MeleeWeapon damage: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml index 4c9f851ba0..ddb7b5f68c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml @@ -18,11 +18,10 @@ - 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: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml index 5fbfb3eb91..f0a48a6577 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml @@ -49,8 +49,10 @@ baseWalkSpeed : 2.00 baseSprintSpeed : 2.00 - type: Bloodstream - bloodMaxVolume: 350 - chemicalMaxVolume: 0 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 350 - type: MobThresholds thresholds: 0: Alive @@ -404,8 +406,10 @@ capacity: 1 count: 1 - type: Bloodstream - bloodMaxVolume: 200 - chemicalMaxVolume: 0 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 200 - type: NpcFactionMember factions: - SimpleHostile diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml index 23e3230937..5c575da396 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml @@ -114,11 +114,10 @@ - type: Input context: "human" - type: Bloodstream - bloodMaxVolume: 300 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Laughter - Quantity: 1 + Quantity: 300 - type: entity name: behonker parent: BaseMobBehonker diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index 79df100598..b430689361 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -349,11 +349,10 @@ speedModifierThresholds: 50: 0.4 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Water - Quantity: 1 - chemicalMaxVolume: 100 + Quantity: 100 - type: StatusEffects allowed: - Electrocution @@ -421,10 +420,10 @@ suffix: Beer components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Beer - Quantity: 1 + Quantity: 100 - type: PointLight color: "#cfa85f" - type: Sprite @@ -441,10 +440,10 @@ suffix: Pax components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Pax - Quantity: 1 + Quantity: 100 - type: PointLight color: "#AAAAAA" - type: Sprite @@ -464,10 +463,10 @@ suffix: Nocturine components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Nocturine - Quantity: 1 + Quantity: 100 - type: PointLight color: "#128e80" - type: Sprite @@ -487,10 +486,10 @@ suffix: THC components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: THC - Quantity: 1 + Quantity: 100 - type: PointLight color: "#808080" - type: Sprite @@ -507,10 +506,10 @@ suffix: Bicaridine components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Bicaridine - Quantity: 1 + Quantity: 100 - type: PointLight color: "#ffaa00" - type: Sprite @@ -527,10 +526,10 @@ suffix: Toxin components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Toxin - Quantity: 1 + Quantity: 100 - type: PointLight color: "#cf3600" - type: Sprite @@ -547,10 +546,10 @@ suffix: Napalm components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Napalm - Quantity: 1 + Quantity: 100 - type: PointLight color: "#FA00AF" - type: Sprite @@ -567,10 +566,10 @@ suffix: Omnizine components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Omnizine - Quantity: 1 + Quantity: 100 - type: PointLight color: "#fcf7f9" - type: Sprite @@ -587,10 +586,10 @@ suffix: Mute Toxin components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: MuteToxin - Quantity: 1 + Quantity: 100 - type: PointLight color: "#0f0f0f" - type: Sprite @@ -607,10 +606,10 @@ suffix: Norepinephric Acid components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: NorepinephricAcid - Quantity: 1 + Quantity: 100 - type: PointLight color: "#96a8b5" - type: Sprite @@ -627,10 +626,10 @@ suffix: Ephedrine components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Ephedrine - Quantity: 1 + Quantity: 100 - type: PointLight color: "#D2FFFA" - type: Sprite @@ -647,10 +646,10 @@ suffix: Robust Harvest components: - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: RobustHarvest - Quantity: 1 + Quantity: 100 - type: PointLight color: "#3e901c" - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml index f3ec5ff80b..75f0aa83b8 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml @@ -48,7 +48,10 @@ - id: FoodMeat amount: 1 - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: CombatMode - type: MeleeWeapon soundHit: @@ -247,7 +250,10 @@ - id: FoodMeat amount: 1 - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: CombatMode - type: MeleeWeapon soundHit: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index ad392145e1..ac4f476a35 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -153,12 +153,10 @@ - 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: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index f9e8e4e33a..d0ea13f26e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -101,7 +101,10 @@ - RadiationProtection - Adrenaline - type: Bloodstream - bloodMaxVolume: 150 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 150 - type: MobPrice price: 150 - type: FloatingVisuals diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index 74c6bb1468..80269feef3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -53,10 +53,10 @@ damageContainer: Biological damageModifierSet: Slime - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Slime - Quantity: 1 + Quantity: 150 bloodlossDamage: types: Bloodloss: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index 8acc571ccf..d052728379 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -39,11 +39,10 @@ critThreshold: 150 - type: MovementAlwaysTouching - type: Bloodstream - bloodMaxVolume: 300 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 300 - type: CombatMode - type: Temperature heatDamageThreshold: 500 @@ -219,11 +218,10 @@ amount: 1 prob: 0.5 - type: Bloodstream - bloodMaxVolume: 250 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 250 - type: Fixtures fixtures: fix1: @@ -325,11 +323,10 @@ amount: 1 prob: 0.3 - type: Bloodstream - bloodMaxVolume: 200 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 200 - type: Fixtures fixtures: fix1: @@ -502,11 +499,10 @@ - type: CombatMode combatToggleAction: ActionCombatModeToggleOff - type: Bloodstream - bloodMaxVolume: 30 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 30 - type: CanEscapeInventory - type: MobPrice price: 50 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index be7c034a39..780c4b0330 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -66,7 +66,10 @@ - id: FoodMeatXeno amount: 1 - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: CombatMode - type: MeleeWeapon soundHit: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index f1c860364b..247d7636e5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -67,11 +67,10 @@ - type: Stamina critThreshold: 200 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: FluorosulfuricAcid - Quantity: 1 - bloodMaxVolume: 650 + Quantity: 650 - type: MeleeWeapon altDisarm: false angle: 0 @@ -495,11 +494,10 @@ - type: Stamina critThreshold: 200 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: FluorosulfuricAcid - Quantity: 1 - bloodMaxVolume: 300 + Quantity: 300 - type: MeleeWeapon altDisarm: false angle: 0 diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index f4e6f1d39c..07bff11974 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -7,7 +7,10 @@ abstract: true components: - type: Bloodstream - bloodMaxVolume: 650 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 650 - type: GhostRole allowMovement: true allowSpeech: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index e38e93a930..43c87f8994 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -42,10 +42,10 @@ - !type:WashCreamPie # Damage (Self) - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: CopperBlood - Quantity: 1 + Quantity: 300 # Damage (Others) - type: MeleeWeapon animation: WeaponArcBite diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 4dafbf99b9..1e9d7ea696 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -33,10 +33,10 @@ - id: FoodMeatPlant amount: 5 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Sap - Quantity: 1 + Quantity: 300 - type: Reactive groups: Flammable: [ Touch ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml b/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml index 7844c80082..794317464d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml @@ -29,12 +29,12 @@ - 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: diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index d0adbcc28f..83031d61d9 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -33,10 +33,10 @@ - id: FoodMeat amount: 5 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 + Quantity: 300 - type: DamageVisuals damageOverlayGroups: Brute: diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 89115dd1c2..6b777f8c3b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -68,10 +68,10 @@ 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: diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index ecc0cea5cd..c0b70ae2b8 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -89,10 +89,10 @@ Burn: sprite: Mobs/Effects/burn_damage.rsi - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: AmmoniaBlood - Quantity: 1 + Quantity: 300 - type: MeleeWeapon soundHit: collection: AlienClaw diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index e2e918152a..a21ca14c5d 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -258,7 +258,7 @@ components: - type: SolutionContainerManager - type: InjectableSolution - solution: chemicals + solution: bloodstream - type: Bloodstream bloodlossDamage: types: diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index c048b27b1a..aa0cf4b6cd 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -982,6 +982,8 @@ - type: reaction id: RedMead + requiredMixerCategories: + - Stir reactants: Mead: amount: 1 diff --git a/Resources/Prototypes/Recipes/Reactions/fun.yml b/Resources/Prototypes/Recipes/Reactions/fun.yml index d27cdefc95..5a3764a504 100644 --- a/Resources/Prototypes/Recipes/Reactions/fun.yml +++ b/Resources/Prototypes/Recipes/Reactions/fun.yml @@ -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: diff --git a/Resources/Prototypes/Recipes/Reactions/single_reagent.yml b/Resources/Prototypes/Recipes/Reactions/single_reagent.yml index d5c0efd21a..64fcfdf920 100644 --- a/Resources/Prototypes/Recipes/Reactions/single_reagent.yml +++ b/Resources/Prototypes/Recipes/Reactions/single_reagent.yml @@ -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: