From 7fc0e3cb26155b6777e4e7661038b07bc3a1168a Mon Sep 17 00:00:00 2001 From: Nikovnik <116634167+nkokic@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:21:16 +0100 Subject: [PATCH] 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> --- .../Body/Components/MetabolizerComponent.cs | 2 +- .../Body/Systems/BloodstreamSystem.cs | 10 +- .../Body/Systems/MetabolizerSystem.cs | 24 +- .../SolutionInjectOnEventSystem.cs | 2 +- .../Fluids/EntitySystems/SmokeSystem.cs | 6 +- .../Medical/HealthAnalyzerSystem.cs | 6 +- .../Nutrition/EntitySystems/SmokingSystem.cs | 2 +- .../Zombies/ZombieSystem.Transform.cs | 2 +- .../Body/Components/BloodstreamComponent.cs | 28 +-- .../Body/Components/StomachComponent.cs | 2 +- .../Body/Events/MetabolismExclusionEvent.cs | 8 + .../Body/Systems/SharedBloodstreamSystem.cs | 192 ++++++++++------ Content.Shared/Devour/DevourSystem.cs | 2 +- .../CleanBloodstreamEntityEffectSystem.cs | 2 +- .../Medical/Cryogenics/SharedCryoPodSystem.cs | 2 +- .../Medical/Healing/HealingSystem.cs | 2 +- Content.Shared/Medical/VomitSystem.cs | 15 +- Content.Shared/Rootable/RootableSystem.cs | 6 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 214 ++++++++++++++---- .../Entities/Mobs/NPCs/argocyte.yml | 5 +- .../Entities/Mobs/NPCs/asteroid.yml | 12 +- .../Entities/Mobs/NPCs/behonker.yml | 5 +- .../Entities/Mobs/NPCs/elemental.yml | 53 +++-- .../Prototypes/Entities/Mobs/NPCs/flesh.yml | 10 +- .../Entities/Mobs/NPCs/miscellaneous.yml | 6 +- .../Entities/Mobs/NPCs/simplemob.yml | 5 +- .../Prototypes/Entities/Mobs/NPCs/slimes.yml | 4 +- .../Prototypes/Entities/Mobs/NPCs/space.yml | 20 +- .../Entities/Mobs/NPCs/spacetick.yml | 5 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 10 +- .../Entities/Mobs/Player/dragon.yml | 5 +- .../Entities/Mobs/Species/arachnid.yml | 4 +- .../Entities/Mobs/Species/diona.yml | 4 +- .../Entities/Mobs/Species/gingerbread.yml | 6 +- .../Prototypes/Entities/Mobs/Species/moth.yml | 4 +- .../Entities/Mobs/Species/slime.yml | 4 +- .../Prototypes/Entities/Mobs/Species/vox.yml | 4 +- Resources/Prototypes/Entities/Mobs/base.yml | 2 +- .../Prototypes/Recipes/Reactions/drinks.yml | 2 + .../Prototypes/Recipes/Reactions/fun.yml | 2 + .../Recipes/Reactions/single_reagent.yml | 2 + 41 files changed, 450 insertions(+), 251 deletions(-) create mode 100644 Content.Shared/Body/Events/MetabolismExclusionEvent.cs 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 5bd0000f5d..3d19708139 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; // Shitmed Change using Content.Shared.Body.Part; @@ -44,7 +45,8 @@ 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 MedicalRecordsSystem _medicalRecords = default!; + [Dependency] private readonly MedicalRecordsSystem _medicalRecords = default!; // DeltaV - Medical Records + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; public override void Initialize() { @@ -297,7 +299,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 c47549b8ba..9bd410e2d3 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -208,7 +208,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/Devour/DevourSystem.cs b/Content.Shared/Devour/DevourSystem.cs index 62dfeb6d2e..c63ffb34f1 100644 --- a/Content.Shared/Devour/DevourSystem.cs +++ b/Content.Shared/Devour/DevourSystem.cs @@ -109,7 +109,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 58e414e176..cf819bfea4 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -112,7 +112,7 @@ public abstract partial class SharedCryoPodSystem : EntitySystem : _solutionContainer.SplitSolution(containerSolution.Value, cryoPod.BeakerTransferAmount); // END DeltaV - _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 8b822c161c..b33dcf6816 100644 --- a/Content.Shared/Medical/Healing/HealingSystem.cs +++ b/Content.Shared/Medical/Healing/HealingSystem.cs @@ -147,7 +147,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); + var vomitChemstreamAmount = _bloodstream.FlushChemicals((uid, bloodStream), vomitAmount); - vomitAmount -= (float)vomitChemstreamAmount.Volume; + 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 53053d87d3..8946eab90d 100644 --- a/Content.Shared/Rootable/RootableSystem.cs +++ b/Content.Shared/Rootable/RootableSystem.cs @@ -103,17 +103,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 c54ca66221..6ac9418050 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -51,7 +51,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 @@ -141,11 +144,10 @@ - Bee - Trash - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 0.1 + Quantity: 0.1 - type: MobPrice price: 50 - type: NPCRetaliation @@ -191,7 +193,10 @@ factions: - SimpleHostile - type: Bloodstream - bloodMaxVolume: 0.1 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 0.1 - type: ZombieImmune @@ -267,7 +272,10 @@ interactSuccessSound: path: /Audio/Animals/chicken_cluck_happy.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: EggLayer eggSpawn: - id: FoodEgg @@ -394,11 +402,10 @@ Dead: Base: cockroach_dead - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 20 + Quantity: 20 - type: Edible - type: FlavorProfile flavors: @@ -562,10 +569,10 @@ damageContainer: Biological damageModifierSet: Moth - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 + Quantity: 20 - type: Respirator damage: types: @@ -725,7 +732,10 @@ interactSuccessSound: path: /Audio/Animals/duck_quack_happy.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: EggLayer eggSpawn: - id: FoodEgg @@ -853,11 +863,10 @@ tags: - Trash - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: InsectBlood - Quantity: 1 - bloodMaxVolume: 0.1 + Quantity: 0.1 - type: MobPrice price: 50 - type: Destructible @@ -1020,11 +1029,10 @@ - type: ReplacementAccent accent: crab - type: Bloodstream - bloodMaxVolume: 50 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: CopperBlood - Quantity: 1 + Quantity: 50 - type: Tag tags: - VimPilot @@ -1193,7 +1201,10 @@ interactSuccessSound: path: /Audio/Animals/goose_honk.ogg - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: NpcFactionMember factions: - Passive @@ -1235,7 +1246,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: @@ -2033,7 +2047,10 @@ types: Piercing: 0 - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: CanEscapeInventory - type: MobPrice price: 50 @@ -2170,10 +2187,10 @@ - type: RadiationSource intensity: 0.3 - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: UnstableMutagen - Quantity: 1 + Quantity: 50 - type: SolutionContainerManager solutions: food: @@ -2251,7 +2268,10 @@ interactSuccessSound: path: /Audio/Animals/lizard_happy.ogg - type: Bloodstream - bloodMaxVolume: 150 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 150 - type: Damageable damageContainer: Biological damageModifierSet: Scale @@ -2307,7 +2327,10 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: entity name: frog @@ -2364,7 +2387,10 @@ interactSuccessSound: path: /Audio/Animals/frog_ribbit.ogg - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: Tag tags: - VimPilot @@ -2456,7 +2482,10 @@ interactSuccessSound: path: /Audio/Animals/parrot_raught.ogg - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: entity name: parrot @@ -2647,7 +2676,10 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts - type: Bloodstream - bloodMaxVolume: 50 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 50 - type: Damageable damageContainer: Biological damageModifierSet: Scale @@ -2716,11 +2748,10 @@ - type: Spider - type: IgnoreSpiderWeb - type: Bloodstream - bloodMaxVolume: 150 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: CopperBlood - Quantity: 1 + Quantity: 150 - type: Speech speechVerb: Arachnid speechSounds: Arachnid @@ -2865,11 +2896,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 @@ -3068,7 +3098,10 @@ attributes: gender: epicene - type: Bloodstream - bloodMaxVolume: 100 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 100 - type: MeleeWeapon angle: 0 animation: WeaponArcBite @@ -3203,10 +3236,10 @@ attributes: gender: epicene - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: DemonsBlood - Quantity: 1 + Quantity: 150 - type: Damageable damageContainer: BiologicalMetaphysical damageModifierSet: Infernal @@ -3747,7 +3780,10 @@ interactSuccessSound: path: /Audio/Animals/fox_squeak.ogg - type: Bloodstream - bloodMaxVolume: 60 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 60 - type: CanEscapeInventory baseResistTime: 3 - type: MobPrice @@ -3867,11 +3903,10 @@ speciesId: cat templateId: pet - type: Bloodstream - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Sap - Quantity: 1 - bloodMaxVolume: 60 + Quantity: 60 - type: DamageStateVisuals states: Alive: @@ -3946,6 +3981,103 @@ - type: ReplacementAccent accent: nymph +- type: entity + name: reindeer buck + parent: [ SimpleMobBase, MobCombat ] + id: MobReindeerBuck + description: You think it can pull a sleigh? + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: reindeer_buck + sprite: Mobs/Animals/reindeer_buck.rsi +# - type: SpriteMovement # One day when this shit isnt broken as hell. For now they hoppity hop. +# movementLayers: +# movement: +# state: reindeer_buck +# noMovementLayers: +# movement: +# state: reindeer_buck_still + - type: Physics + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.48 + density: 200 + mask: + - MobMask + layer: + - MobLayer + - type: DamageStateVisuals + states: + Alive: + Base: reindeer_buck + Critical: + Base: reindeer_buck_dead + Dead: + Base: reindeer_buck_dead + - type: Butcherable + spawned: + - id: FoodMeat + amount: 4 + - type: Bloodstream + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 300 + # Horns though + - type: MeleeWeapon + damage: + types: + Piercing: 22 + animation: WeaponArcFist + - type: NPCRetaliation + - type: FactionException + - type: NpcFactionMember + factions: + - Passive + - type: HTN + rootTask: + task: SimpleHostileCompound + - type: Puller + needsHands: false + +- type: entity + name: reindeer doe + parent: MobReindeerBuck + id: MobReindeerDoe + components: + - type: MeleeWeapon + damage: + types: + Blunt: 15 + animation: WeaponArcFist + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: reindeer_doe + sprite: Mobs/Animals/reindeer_doe.rsi +# - type: SpriteMovement # One day when this shit isnt broken as hell. For now they hoppity hop. +# movementLayers: +# movement: +# state: reindeer_doe +# noMovementLayers: +# movement: +# state: reindeer_doe_still + - type: DamageStateVisuals + states: + Alive: + Base: reindeer_doe + Critical: + Base: reindeer_doe_dead + Dead: + Base: reindeer_doe_dead + - type: entity parent: MobCorgiBase id: MobCorgiSmart 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 c8887b8ce8..3bcb592345 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/asteroid.yml @@ -51,8 +51,10 @@ baseWalkSpeed : 2.00 baseSprintSpeed : 2.00 - type: Bloodstream - bloodMaxVolume: 350 - chemicalMaxVolume: 0 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 350 - type: MobThresholds thresholds: 0: Alive @@ -413,8 +415,10 @@ capacity: 1 count: 1 - type: Bloodstream - bloodMaxVolume: 200 - chemicalMaxVolume: 0 + bloodReferenceSolution: + reagents: + - ReagentId: Blood + Quantity: 200 - type: NpcFactionMember factions: - SalvageMob # DeltaV - Changed to SalvageMob diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml index b5cd568088..c0220cee13 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 a434f1e4fe..45e2440ab7 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 d4ba48d221..77d13c376a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -155,12 +155,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 bb37b656fa..8b1da7cefc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -103,7 +103,10 @@ - Adrenaline - Anesthesia # DeltaV - Anesthesia - 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 86e0450b96..5dc526f662 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -54,10 +54,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 5aa4be9b47..0c77cc73bf 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 @@ -215,11 +214,10 @@ amount: 1 prob: 0.5 - type: Bloodstream - bloodMaxVolume: 250 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 250 - type: Fixtures fixtures: fix1: @@ -323,11 +321,10 @@ amount: 1 prob: 0.3 - type: Bloodstream - bloodMaxVolume: 200 - bloodReagents: + bloodReferenceSolution: reagents: - ReagentId: Cryoxadone - Quantity: 1 + Quantity: 200 - type: Fixtures fixtures: fix1: @@ -494,11 +491,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 5b5fb81628..1b379a4f3c 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 a9a46cf387..326883fcaf 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 @@ -525,11 +524,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 368712594e..871c1d03a1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -9,7 +9,10 @@ - type: Body # Shitmed prototype: SpaceDragon - 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 3a2279a2c8..f43eaaa82e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -43,10 +43,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 aa44ccbf1b..79da2d2c58 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 d5ff093b09..8f7d2d2f94 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 c3bc7e5d5e..c8e3c1aee1 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -74,10 +74,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 1c0d902424..3adc024b56 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 5188394987..924f1837d4 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -261,7 +261,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 224db693fc..ab63eeb3d8 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -984,6 +984,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 4ac24061f6..4e250c43dc 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 b5c4653df3..8cb81307e3 100644 --- a/Resources/Prototypes/Recipes/Reactions/single_reagent.yml +++ b/Resources/Prototypes/Recipes/Reactions/single_reagent.yml @@ -24,6 +24,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: