multi reagent bloodstream (#41489)

* multi reagent bloodstream

* pluralize the comments

* fix TryModifyBloodLevel return logic

* now with quantity

* now with solution

* implement suggestions

* fix forensics

* minor thing

* Nevermind undo that caps matters.

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Ignaz "Ian" Kraft 2025-12-01 06:35:21 +01:00 committed by BarryNorfolk
parent 38bb9232c0
commit c292094b77
25 changed files with 307 additions and 89 deletions

View File

@ -37,7 +37,10 @@ public sealed class BloodstreamSystem : SharedBloodstreamSystem
// Fill blood solution with BLOOD // Fill blood solution with BLOOD
// The DNA string might not be initialized yet, but the reagent data gets updated in the GenerateDnaEvent subscription // The DNA string might not be initialized yet, but the reagent data gets updated in the GenerateDnaEvent subscription
bloodSolution.AddReagent(new ReagentId(entity.Comp.BloodReagent, GetEntityBloodData(entity.Owner)), entity.Comp.BloodMaxVolume - bloodSolution.Volume); var solution = entity.Comp.BloodReagents.Clone();
solution.ScaleTo(entity.Comp.BloodMaxVolume - bloodSolution.Volume);
solution.SetReagentData(GetEntityBloodData(entity.Owner));
bloodSolution.AddSolution(solution, PrototypeManager);
} }
// forensics is not predicted yet // forensics is not predicted yet

View File

@ -1,3 +1,4 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Storage; using Content.Shared.Storage;
namespace Content.Server.Medical.BiomassReclaimer namespace Content.Server.Medical.BiomassReclaimer
@ -34,10 +35,10 @@ namespace Content.Server.Medical.BiomassReclaimer
public float CurrentExpectedYield = 0f; public float CurrentExpectedYield = 0f;
/// <summary> /// <summary>
/// The reagent that will be spilled while processing a mob. /// The reagents that will be spilled while processing a mob.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public string? BloodReagent; public Solution? BloodReagents = null;
/// <summary> /// <summary>
/// Entities that can be randomly spawned while processing a mob. /// Entities that can be randomly spawned while processing a mob.

View File

@ -7,7 +7,7 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Climbing.Events; using Content.Shared.Climbing.Events;
using Content.Shared.Construction.Components; using Content.Shared.Construction.Components;
using Content.Shared.Database; using Content.Shared.Database;
@ -45,6 +45,7 @@ namespace Content.Server.Medical.BiomassReclaimer
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!; [Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
@ -68,10 +69,8 @@ namespace Content.Server.Medical.BiomassReclaimer
if (reclaimer.RandomMessTimer <= 0) if (reclaimer.RandomMessTimer <= 0)
{ {
if (_robustRandom.Prob(0.2f) && reclaimer.BloodReagent is not null) if (_robustRandom.Prob(0.2f) && reclaimer.BloodReagents is { } blood)
{ {
Solution blood = new();
blood.AddReagent(reclaimer.BloodReagent, 50);
_puddleSystem.TrySpillAt(uid, blood, out _); _puddleSystem.TrySpillAt(uid, blood, out _);
} }
if (_robustRandom.Prob(0.03f) && reclaimer.SpawnedEntities.Count > 0) if (_robustRandom.Prob(0.03f) && reclaimer.SpawnedEntities.Count > 0)
@ -92,7 +91,7 @@ namespace Content.Server.Medical.BiomassReclaimer
reclaimer.CurrentExpectedYield = reclaimer.CurrentExpectedYield - actualYield; // store non-integer leftovers reclaimer.CurrentExpectedYield = reclaimer.CurrentExpectedYield - actualYield; // store non-integer leftovers
_material.SpawnMultipleFromMaterial(actualYield, BiomassPrototype, Transform(uid).Coordinates); _material.SpawnMultipleFromMaterial(actualYield, BiomassPrototype, Transform(uid).Coordinates);
reclaimer.BloodReagent = null; reclaimer.BloodReagents = null;
reclaimer.SpawnedEntities.Clear(); reclaimer.SpawnedEntities.Clear();
RemCompDeferred<ActiveBiomassReclaimerComponent>(uid); RemCompDeferred<ActiveBiomassReclaimerComponent>(uid);
} }
@ -208,9 +207,11 @@ namespace Content.Server.Medical.BiomassReclaimer
var component = ent.Comp; var component = ent.Comp;
AddComp<ActiveBiomassReclaimerComponent>(ent); AddComp<ActiveBiomassReclaimerComponent>(ent);
if (TryComp<BloodstreamComponent>(toProcess, out var stream)) if (TryComp<BloodstreamComponent>(toProcess, out var stream) &&
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
{ {
component.BloodReagent = stream.BloodReagent; component.BloodReagents = solution.Clone();
component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume);
} }
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent)) if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
{ {

View File

@ -17,7 +17,6 @@ using Content.Shared.Abilities.Psionics; // DeltaV
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
using Content.Shared.CombatMode.Pacification; using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage.Components;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
@ -209,8 +208,8 @@ public sealed partial class ZombieSystem
zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor;
zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor; zombiecomp.BeforeZombifiedEyeColor = huApComp.EyeColor;
zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers); zombiecomp.BeforeZombifiedCustomBaseLayers = new(huApComp.CustomBaseLayers);
if (TryComp<BloodstreamComponent>(target, out var stream)) if (TryComp<BloodstreamComponent>(target, out var stream) && stream.BloodReagents is { } reagents)
zombiecomp.BeforeZombifiedBloodReagent = stream.BloodReagent; zombiecomp.BeforeZombifiedBloodReagents = reagents.Clone();
_humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp); _humanoidAppearance.SetSkinColor(target, zombiecomp.SkinColor, verify: false, humanoid: huApComp);
@ -244,7 +243,7 @@ public sealed partial class ZombieSystem
//NOTE: they are supposed to bleed, just not take damage //NOTE: they are supposed to bleed, just not take damage
_bloodstream.SetBloodLossThreshold(target, 0f); _bloodstream.SetBloodLossThreshold(target, 0f);
//Give them zombie blood //Give them zombie blood
_bloodstream.ChangeBloodReagent(target, zombiecomp.NewBloodReagent); _bloodstream.ChangeBloodReagents(target, zombiecomp.NewBloodReagents);
//This is specifically here to combat insuls, because frying zombies on grilles is funny as shit. //This is specifically here to combat insuls, because frying zombies on grilles is funny as shit.
//_inventory.TryUnequip(target, "gloves", true, true); // DeltaV - Buff Zombies //_inventory.TryUnequip(target, "gloves", true, true); // DeltaV - Buff Zombies

View File

@ -305,7 +305,7 @@ namespace Content.Server.Zombies
appcomp.EyeColor = zombiecomp.BeforeZombifiedEyeColor; appcomp.EyeColor = zombiecomp.BeforeZombifiedEyeColor;
} }
_humanoidAppearance.SetSkinColor(target, zombiecomp.BeforeZombifiedSkinColor, false); _humanoidAppearance.SetSkinColor(target, zombiecomp.BeforeZombifiedSkinColor, false);
_bloodstream.ChangeBloodReagent(target, zombiecomp.BeforeZombifiedBloodReagent); _bloodstream.ChangeBloodReagents(target, zombiecomp.BeforeZombifiedBloodReagents);
return true; return true;
} }

View File

@ -1,7 +1,6 @@
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Body.Systems; using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
@ -152,13 +151,13 @@ public sealed partial class BloodstreamComponent : Component
public FixedPoint2 BloodMaxVolume = FixedPoint2.New(300); public FixedPoint2 BloodMaxVolume = FixedPoint2.New(300);
/// <summary> /// <summary>
/// Which reagent is considered this entities 'blood'? /// Which reagents are considered this entities 'blood'?
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Slime-people might use slime as their blood or something like that. /// Slime-people might use slime as their blood or something like that.
/// </remarks> /// </remarks>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public ProtoId<ReagentPrototype> BloodReagent = "Blood"; public Solution BloodReagents = new([new("Blood", 1)]);
/// <summary> /// <summary>
/// Name/Key that <see cref="BloodSolution"/> is indexed by. /// Name/Key that <see cref="BloodSolution"/> is indexed by.

View File

@ -29,9 +29,9 @@ public abstract class SharedBloodstreamSystem : EntitySystem
{ {
public static readonly EntProtoId Bloodloss = "StatusEffectBloodloss"; public static readonly EntProtoId Bloodloss = "StatusEffectBloodloss";
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!; [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedPuddleSystem _puddle = default!; [Dependency] private readonly SharedPuddleSystem _puddle = default!;
@ -193,7 +193,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
} }
// TODO probably cache this or something. humans get hurt a lot // TODO probably cache this or something. humans get hurt a lot
if (!_prototypeManager.Resolve(ent.Comp.DamageBleedModifiers, out var modifiers)) if (!PrototypeManager.Resolve(ent.Comp.DamageBleedModifiers, out var modifiers))
return; return;
// some reagents may deal and heal different damage types in the same tick, which means DamageIncreased will be true // some reagents may deal and heal different damage types in the same tick, which means DamageIncreased will be true
@ -366,11 +366,18 @@ public abstract class SharedBloodstreamSystem : EntitySystem
public bool TryModifyBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount) public bool TryModifyBloodLevel(Entity<BloodstreamComponent?> ent, FixedPoint2 amount)
{ {
if (!Resolve(ent, ref ent.Comp, logMissing: false) if (!Resolve(ent, ref ent.Comp, logMissing: false)
|| !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution)) || !SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
return false; return false;
if (amount >= 0) if (amount >= 0)
return SolutionContainer.TryAddReagent(ent.Comp.BloodSolution.Value, ent.Comp.BloodReagent, amount, null, GetEntityBloodData(ent)); {
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;
}
// Removal is more involved, // Removal is more involved,
// since we also wanna handle moving it to the temporary solution // since we also wanna handle moving it to the temporary solution
@ -380,7 +387,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution)) if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
return true; return true;
tempSolution.AddSolution(newSol, _prototypeManager); tempSolution.AddSolution(newSol, PrototypeManager);
if (tempSolution.Volume > ent.Comp.BleedPuddleThreshold) if (tempSolution.Volume > ent.Comp.BleedPuddleThreshold)
{ {
@ -388,7 +395,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution)) if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution))
{ {
var temp = SolutionContainer.SplitSolution(ent.Comp.ChemicalSolution.Value, tempSolution.Volume / 10); var temp = SolutionContainer.SplitSolution(ent.Comp.ChemicalSolution.Value, tempSolution.Volume / 10);
tempSolution.AddSolution(temp, _prototypeManager); tempSolution.AddSolution(temp, PrototypeManager);
} }
_puddle.TrySpillAt(ent.Owner, tempSolution, out _, sound: false); _puddle.TrySpillAt(ent.Owner, tempSolution, out _, sound: false);
@ -439,21 +446,21 @@ public abstract class SharedBloodstreamSystem : EntitySystem
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution)) if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
{ {
tempSol.MaxVolume += bloodSolution.MaxVolume; tempSol.MaxVolume += bloodSolution.MaxVolume;
tempSol.AddSolution(bloodSolution, _prototypeManager); tempSol.AddSolution(bloodSolution, PrototypeManager);
SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value); SolutionContainer.RemoveAllSolution(ent.Comp.BloodSolution.Value);
} }
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution, out var chemSolution)) if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.ChemicalSolutionName, ref ent.Comp.ChemicalSolution, out var chemSolution))
{ {
tempSol.MaxVolume += chemSolution.MaxVolume; tempSol.MaxVolume += chemSolution.MaxVolume;
tempSol.AddSolution(chemSolution, _prototypeManager); tempSol.AddSolution(chemSolution, PrototypeManager);
SolutionContainer.RemoveAllSolution(ent.Comp.ChemicalSolution.Value); SolutionContainer.RemoveAllSolution(ent.Comp.ChemicalSolution.Value);
} }
if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution)) if (SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodTemporarySolutionName, ref ent.Comp.TemporarySolution, out var tempSolution))
{ {
tempSol.MaxVolume += tempSolution.MaxVolume; tempSol.MaxVolume += tempSolution.MaxVolume;
tempSol.AddSolution(tempSolution, _prototypeManager); tempSol.AddSolution(tempSolution, PrototypeManager);
SolutionContainer.RemoveAllSolution(ent.Comp.TemporarySolution.Value); SolutionContainer.RemoveAllSolution(ent.Comp.TemporarySolution.Value);
} }
@ -463,27 +470,45 @@ public abstract class SharedBloodstreamSystem : EntitySystem
/// <summary> /// <summary>
/// Change what someone's blood is made of, on the fly. /// Change what someone's blood is made of, on the fly.
/// </summary> /// </summary>
[Obsolete("ChangeBloodReagent is obsolete, please use ChangeBloodReagents.")]
public void ChangeBloodReagent(Entity<BloodstreamComponent?> ent, ProtoId<ReagentPrototype> reagent) public void ChangeBloodReagent(Entity<BloodstreamComponent?> ent, ProtoId<ReagentPrototype> reagent)
{ {
if (!Resolve(ent, ref ent.Comp, logMissing: false) ChangeBloodReagents(ent, new([new(reagent, 1)]));
|| reagent == ent.Comp.BloodReagent) }
/// <summary>
/// Change what someone's blood is made of, on the fly.
/// </summary>
public void ChangeBloodReagents(Entity<BloodstreamComponent?> ent, Solution reagents)
{
if (!Resolve(ent, ref ent.Comp, logMissing: false))
{ {
return; return;
} }
if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution)) if (!SolutionContainer.ResolveSolution(ent.Owner, ent.Comp.BloodSolutionName, ref ent.Comp.BloodSolution, out var bloodSolution))
{ {
ent.Comp.BloodReagent = reagent; ent.Comp.BloodReagents = reagents.Clone();
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReagents));
return; return;
} }
var currentVolume = bloodSolution.RemoveReagent(ent.Comp.BloodReagent, bloodSolution.Volume, ignoreReagentData: true); var currentVolume = FixedPoint2.Zero;
foreach (var reagent in ent.Comp.BloodReagents)
{
currentVolume += bloodSolution.RemoveReagent(reagent.Reagent, quantity: bloodSolution.Volume, ignoreReagentData: true);
}
ent.Comp.BloodReagent = reagent; ent.Comp.BloodReagents = reagents.Clone();
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReagent)); DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.BloodReagents));
if (currentVolume > 0) if (currentVolume == FixedPoint2.Zero)
SolutionContainer.TryAddReagent(ent.Comp.BloodSolution.Value, ent.Comp.BloodReagent, currentVolume, null, GetEntityBloodData(ent)); return;
var solution = ent.Comp.BloodReagents.Clone();
solution.ScaleSolution(currentVolume / solution.Volume);
solution.SetReagentData(GetEntityBloodData(ent));
SolutionContainer.AddSolution(ent.Comp.BloodSolution.Value, solution);
} }
/// <summary> /// <summary>

View File

@ -167,7 +167,12 @@ namespace Content.Shared.Chemistry.Components
public Solution(Solution solution) public Solution(Solution solution)
{ {
Contents = solution.Contents.ShallowClone(); Contents = new(solution.Contents.Count);
foreach (var item in solution.Contents)
{
Contents.Add(item.Clone());
}
Volume = solution.Volume; Volume = solution.Volume;
MaxVolume = solution.MaxVolume; MaxVolume = solution.MaxVolume;
Temperature = solution.Temperature; Temperature = solution.Temperature;
@ -474,6 +479,37 @@ namespace Content.Shared.Chemistry.Components
ValidateSolution(); ValidateSolution();
} }
/// <summary>
/// Scales the amount of solution.
/// </summary>
/// <param name="scale">The scalar to modify the solution by.</param>
public void ScaleSolution(FixedPoint2 scale)
{
ScaleSolution(scale.Float());
}
/// <summary>
/// Scales the amount of solution to a given target.
/// </summary>
/// <param name="target">The volume the solutions should have after scaling.</param>
public void ScaleTo(FixedPoint2 target)
{
if (Volume == FixedPoint2.Zero || Volume == target)
return;
if (target == FixedPoint2.Zero)
{
RemoveAllSolution();
return;
}
var nearestIntScale = (int)Math.Ceiling(target.Float() / Volume.Float());
ScaleSolution(nearestIntScale);
var overflow = Volume - target;
RemoveSolution(overflow);
}
/// <summary> /// <summary>
/// Attempts to remove an amount of reagent from the solution. /// Attempts to remove an amount of reagent from the solution.
/// </summary> /// </summary>
@ -965,5 +1001,15 @@ namespace Content.Shared.Chemistry.Components
} }
return dict; return dict;
} }
public void SetReagentData(List<ReagentData>? data)
{
for (var i = 0; i < Contents.Count; i++)
{
var old = Contents[i];
Contents[i] = new ReagentQuantity(new ReagentId(old.Reagent.Prototype, data), old.Quantity);
}
ValidateSolution();
}
} }
} }

View File

@ -1,4 +1,3 @@
using Content.Shared.FixedPoint;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.Reagent; namespace Content.Shared.Chemistry.Reagent;
@ -7,9 +6,15 @@ namespace Content.Shared.Chemistry.Reagent;
public sealed partial class DnaData : ReagentData public sealed partial class DnaData : ReagentData
{ {
[DataField] [DataField]
public string DNA = String.Empty; public string DNA = string.Empty;
public override ReagentData Clone() => this; public override ReagentData Clone()
{
return new DnaData
{
DNA = DNA,
};
}
public override bool Equals(ReagentData? other) public override bool Equals(ReagentData? other)
{ {

View File

@ -8,9 +8,9 @@ namespace Content.Shared.Chemistry.Reagent;
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
[DataDefinition] [DataDefinition]
public partial struct ReagentQuantity : IEquatable<ReagentQuantity> public partial struct ReagentQuantity : IEquatable<ReagentQuantity>, IRobustCloneable<ReagentQuantity>
{ {
[DataField("Quantity", required:true)] [DataField("Quantity", required: true)]
public FixedPoint2 Quantity { get; private set; } public FixedPoint2 Quantity { get; private set; }
[IncludeDataField] [IncludeDataField]
@ -28,6 +28,28 @@ public partial struct ReagentQuantity : IEquatable<ReagentQuantity>
Quantity = quantity; Quantity = quantity;
} }
public ReagentQuantity(ReagentQuantity reagentQuantity)
{
Quantity = reagentQuantity.Quantity;
if (reagentQuantity.Reagent.Data is not { } data)
{
Reagent = new ReagentId(reagentQuantity.Reagent.Prototype, null);
return;
}
List<ReagentData> copy = new(data.Count);
foreach (var item in data)
{
copy.Add(item.Clone());
}
Reagent = new ReagentId(reagentQuantity.Reagent.Prototype, copy);
}
public readonly ReagentQuantity Clone()
{
return new ReagentQuantity(this);
}
public ReagentQuantity() : this(default, default) public ReagentQuantity() : this(default, default)
{ {
} }

View File

@ -1,7 +1,7 @@
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.StatusIcon; using Content.Shared.StatusIcon;
@ -167,14 +167,14 @@ public sealed partial class ZombieComponent : Component
public SoundSpecifier BiteSound = new SoundPathSpecifier("/Audio/Effects/bite.ogg"); public SoundSpecifier BiteSound = new SoundPathSpecifier("/Audio/Effects/bite.ogg");
/// <summary> /// <summary>
/// The blood reagent of the humanoid to restore in case of cloning /// The blood reagents of the humanoid to restore in case of cloning
/// </summary> /// </summary>
[DataField("beforeZombifiedBloodReagent")] [DataField("beforeZombifiedBloodReagents")]
public string BeforeZombifiedBloodReagent = string.Empty; public Solution BeforeZombifiedBloodReagents = new();
/// <summary> /// <summary>
/// The blood reagent to give the zombie. In case you want zombies that bleed milk, or something. /// The blood reagents to give the zombie. In case you want zombies that bleed milk, or something.
/// </summary> /// </summary>
[DataField("newBloodReagent", customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))] [DataField("newBloodReagents")]
public string NewBloodReagent = "ZombieBlood"; public Solution NewBloodReagents = new([new("ZombieBlood", 1)]);
} }

View File

@ -141,7 +141,10 @@
- Bee - Bee
- Trash - Trash
- type: Bloodstream - type: Bloodstream
bloodReagent: InsectBlood bloodReagents:
reagents:
- ReagentId: InsectBlood
Quantity: 1
bloodMaxVolume: 0.1 bloodMaxVolume: 0.1
- type: MobPrice - type: MobPrice
price: 50 price: 50
@ -391,7 +394,10 @@
Dead: Dead:
Base: cockroach_dead Base: cockroach_dead
- type: Bloodstream - type: Bloodstream
bloodReagent: InsectBlood bloodReagents:
reagents:
- ReagentId: InsectBlood
Quantity: 1
bloodMaxVolume: 20 bloodMaxVolume: 20
- type: Edible - type: Edible
- type: FlavorProfile - type: FlavorProfile
@ -556,7 +562,10 @@
damageContainer: Biological damageContainer: Biological
damageModifierSet: Moth damageModifierSet: Moth
- type: Bloodstream - type: Bloodstream
bloodReagent: InsectBlood bloodReagents:
reagents:
- ReagentId: InsectBlood
Quantity: 1
- type: Respirator - type: Respirator
damage: damage:
types: types:
@ -844,7 +853,10 @@
tags: tags:
- Trash - Trash
- type: Bloodstream - type: Bloodstream
bloodReagent: InsectBlood bloodReagents:
reagents:
- ReagentId: InsectBlood
Quantity: 1
bloodMaxVolume: 0.1 bloodMaxVolume: 0.1
- type: MobPrice - type: MobPrice
price: 50 price: 50
@ -1009,7 +1021,10 @@
accent: crab accent: crab
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
bloodReagent: CopperBlood bloodReagents:
reagents:
- ReagentId: CopperBlood
Quantity: 1
- type: Tag - type: Tag
tags: tags:
- VimPilot - VimPilot
@ -2155,7 +2170,10 @@
- type: RadiationSource - type: RadiationSource
intensity: 0.3 intensity: 0.3
- type: Bloodstream - type: Bloodstream
bloodReagent: UnstableMutagen bloodReagents:
reagents:
- ReagentId: UnstableMutagen
Quantity: 1
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
food: food:
@ -2699,7 +2717,10 @@
- type: IgnoreSpiderWeb - type: IgnoreSpiderWeb
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 150 bloodMaxVolume: 150
bloodReagent: CopperBlood bloodReagents:
reagents:
- ReagentId: CopperBlood
Quantity: 1
- type: Speech - type: Speech
speechVerb: Arachnid speechVerb: Arachnid
speechSounds: Arachnid speechSounds: Arachnid
@ -2845,8 +2866,10 @@
speechVerb: Cluwne speechVerb: Cluwne
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 150 bloodMaxVolume: 150
bloodReagent: Laughter bloodReagents:
reagents:
- ReagentId: Laughter
Quantity: 1
- type: entity - type: entity
name: wizard spider name: wizard spider
parent: MobGiantSpider parent: MobGiantSpider
@ -3180,7 +3203,10 @@
attributes: attributes:
gender: epicene gender: epicene
- type: Bloodstream - type: Bloodstream
bloodReagent: DemonsBlood bloodReagents:
reagents:
- ReagentId: DemonsBlood
Quantity: 1
- type: Damageable - type: Damageable
damageContainer: BiologicalMetaphysical damageContainer: BiologicalMetaphysical
damageModifierSet: Infernal damageModifierSet: Infernal
@ -3841,7 +3867,10 @@
speciesId: cat speciesId: cat
templateId: pet templateId: pet
- type: Bloodstream - type: Bloodstream
bloodReagent: Sap bloodReagents:
reagents:
- ReagentId: Sap
Quantity: 1
bloodMaxVolume: 60 bloodMaxVolume: 60
- type: DamageStateVisuals - type: DamageStateVisuals
states: states:

View File

@ -18,7 +18,10 @@
- type: ReplacementAccent - type: ReplacementAccent
accent: xeno accent: xeno
- type: Bloodstream - type: Bloodstream
bloodReagent: FerrochromicAcid bloodReagents:
reagents:
- ReagentId: FerrochromicAcid
Quantity: 1
bloodMaxVolume: 75 #we don't want the map to become pools of blood bloodMaxVolume: 75 #we don't want the map to become pools of blood
bloodlossDamage: bloodlossDamage:
types: types:

View File

@ -115,8 +115,10 @@
context: "human" context: "human"
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 300 bloodMaxVolume: 300
bloodReagent: Laughter bloodReagents:
reagents:
- ReagentId: Laughter
Quantity: 1
- type: entity - type: entity
name: behonker name: behonker
parent: BaseMobBehonker parent: BaseMobBehonker

View File

@ -349,7 +349,10 @@
speedModifierThresholds: speedModifierThresholds:
50: 0.4 50: 0.4
- type: Bloodstream - type: Bloodstream
bloodReagent: Water bloodReagents:
reagents:
- ReagentId: Water
Quantity: 1
chemicalMaxVolume: 100 chemicalMaxVolume: 100
- type: StatusEffects - type: StatusEffects
allowed: allowed:
@ -418,7 +421,10 @@
suffix: Beer suffix: Beer
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Beer bloodReagents:
reagents:
- ReagentId: Beer
Quantity: 1
- type: PointLight - type: PointLight
color: "#cfa85f" color: "#cfa85f"
- type: Sprite - type: Sprite
@ -435,7 +441,10 @@
suffix: Pax suffix: Pax
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Pax bloodReagents:
reagents:
- ReagentId: Pax
Quantity: 1
- type: PointLight - type: PointLight
color: "#AAAAAA" color: "#AAAAAA"
- type: Sprite - type: Sprite
@ -455,7 +464,10 @@
suffix: Nocturine suffix: Nocturine
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Nocturine bloodReagents:
reagents:
- ReagentId: Nocturine
Quantity: 1
- type: PointLight - type: PointLight
color: "#128e80" color: "#128e80"
- type: Sprite - type: Sprite
@ -475,7 +487,10 @@
suffix: THC suffix: THC
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: THC bloodReagents:
reagents:
- ReagentId: THC
Quantity: 1
- type: PointLight - type: PointLight
color: "#808080" color: "#808080"
- type: Sprite - type: Sprite
@ -492,7 +507,10 @@
suffix: Bicaridine suffix: Bicaridine
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Bicaridine bloodReagents:
reagents:
- ReagentId: Bicaridine
Quantity: 1
- type: PointLight - type: PointLight
color: "#ffaa00" color: "#ffaa00"
- type: Sprite - type: Sprite
@ -509,7 +527,10 @@
suffix: Toxin suffix: Toxin
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Toxin bloodReagents:
reagents:
- ReagentId: Toxin
Quantity: 1
- type: PointLight - type: PointLight
color: "#cf3600" color: "#cf3600"
- type: Sprite - type: Sprite
@ -526,7 +547,10 @@
suffix: Napalm suffix: Napalm
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Napalm bloodReagents:
reagents:
- ReagentId: Napalm
Quantity: 1
- type: PointLight - type: PointLight
color: "#FA00AF" color: "#FA00AF"
- type: Sprite - type: Sprite
@ -543,7 +567,10 @@
suffix: Omnizine suffix: Omnizine
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Omnizine bloodReagents:
reagents:
- ReagentId: Omnizine
Quantity: 1
- type: PointLight - type: PointLight
color: "#fcf7f9" color: "#fcf7f9"
- type: Sprite - type: Sprite
@ -560,7 +587,10 @@
suffix: Mute Toxin suffix: Mute Toxin
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: MuteToxin bloodReagents:
reagents:
- ReagentId: MuteToxin
Quantity: 1
- type: PointLight - type: PointLight
color: "#0f0f0f" color: "#0f0f0f"
- type: Sprite - type: Sprite
@ -577,7 +607,10 @@
suffix: Norepinephric Acid suffix: Norepinephric Acid
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: NorepinephricAcid bloodReagents:
reagents:
- ReagentId: NorepinephricAcid
Quantity: 1
- type: PointLight - type: PointLight
color: "#96a8b5" color: "#96a8b5"
- type: Sprite - type: Sprite
@ -594,7 +627,10 @@
suffix: Ephedrine suffix: Ephedrine
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: Ephedrine bloodReagents:
reagents:
- ReagentId: Ephedrine
Quantity: 1
- type: PointLight - type: PointLight
color: "#D2FFFA" color: "#D2FFFA"
- type: Sprite - type: Sprite
@ -611,7 +647,10 @@
suffix: Robust Harvest suffix: Robust Harvest
components: components:
- type: Bloodstream - type: Bloodstream
bloodReagent: RobustHarvest bloodReagents:
reagents:
- ReagentId: RobustHarvest
Quantity: 1
- type: PointLight - type: PointLight
color: "#3e901c" color: "#3e901c"
- type: Sprite - type: Sprite

View File

@ -152,7 +152,10 @@
- map: [ "enum.DamageStateVisualLayers.Base" ] - map: [ "enum.DamageStateVisualLayers.Base" ]
state: alive state: alive
- type: Bloodstream - type: Bloodstream
bloodReagent: JuiceTomato bloodReagents:
reagents:
- ReagentId: JuiceTomato
Quantity: 1
bloodMaxVolume: 50 bloodMaxVolume: 50
chemicalMaxVolume: 30 chemicalMaxVolume: 30
- type: DamageStateVisuals - type: DamageStateVisuals

View File

@ -54,7 +54,10 @@
damageContainer: Biological damageContainer: Biological
damageModifierSet: Slime damageModifierSet: Slime
- type: Bloodstream - type: Bloodstream
bloodReagent: Slime bloodReagents:
reagents:
- ReagentId: Slime
Quantity: 1
bloodlossDamage: bloodlossDamage:
types: types:
Bloodloss: Bloodloss:

View File

@ -40,7 +40,10 @@
- type: MovementAlwaysTouching - type: MovementAlwaysTouching
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 300 bloodMaxVolume: 300
bloodReagent: Cryoxadone bloodReagents:
reagents:
- ReagentId: Cryoxadone
Quantity: 1
- type: CombatMode - type: CombatMode
- type: Temperature - type: Temperature
heatDamageThreshold: 500 heatDamageThreshold: 500
@ -213,7 +216,10 @@
prob: 0.5 prob: 0.5
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 250 bloodMaxVolume: 250
bloodReagent: Cryoxadone bloodReagents:
reagents:
- ReagentId: Cryoxadone
Quantity: 1
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@ -318,7 +324,10 @@
prob: 0.3 prob: 0.3
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 200 bloodMaxVolume: 200
bloodReagent: Cryoxadone bloodReagents:
reagents:
- ReagentId: Cryoxadone
Quantity: 1
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:
@ -486,7 +495,10 @@
combatToggleAction: ActionCombatModeToggleOff combatToggleAction: ActionCombatModeToggleOff
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 30 bloodMaxVolume: 30
bloodReagent: Cryoxadone bloodReagents:
reagents:
- ReagentId: Cryoxadone
Quantity: 1
- type: CanEscapeInventory - type: CanEscapeInventory
- type: MobPrice - type: MobPrice
price: 50 price: 50

View File

@ -67,7 +67,10 @@
- type: Stamina - type: Stamina
critThreshold: 200 critThreshold: 200
- type: Bloodstream - type: Bloodstream
bloodReagent: FluorosulfuricAcid bloodReagents:
reagents:
- ReagentId: FluorosulfuricAcid
Quantity: 1
bloodMaxVolume: 650 bloodMaxVolume: 650
- type: MeleeWeapon - type: MeleeWeapon
altDisarm: false altDisarm: false
@ -522,7 +525,10 @@
- type: Stamina - type: Stamina
critThreshold: 200 critThreshold: 200
- type: Bloodstream - type: Bloodstream
bloodReagent: FluorosulfuricAcid bloodReagents:
reagents:
- ReagentId: FluorosulfuricAcid
Quantity: 1
bloodMaxVolume: 300 bloodMaxVolume: 300
- type: MeleeWeapon - type: MeleeWeapon
altDisarm: false altDisarm: false

View File

@ -43,7 +43,10 @@
- !type:WashCreamPie - !type:WashCreamPie
# Damage (Self) # Damage (Self)
- type: Bloodstream - type: Bloodstream
bloodReagent: CopperBlood bloodReagents:
reagents:
- ReagentId: CopperBlood
Quantity: 1
# Damage (Others) # Damage (Others)
- type: MeleeWeapon - type: MeleeWeapon
animation: WeaponArcBite animation: WeaponArcBite

View File

@ -33,7 +33,10 @@
- id: FoodMeatPlant - id: FoodMeatPlant
amount: 5 amount: 5
- type: Bloodstream - type: Bloodstream
bloodReagent: Sap bloodReagents:
reagents:
- ReagentId: Sap
Quantity: 1
- type: Reactive - type: Reactive
groups: groups:
Flammable: [ Touch ] Flammable: [ Touch ]

View File

@ -29,7 +29,12 @@
- id: FoodBakedCookie #should be replaced with gingerbread sheets or something... provided you're willing to make a full spriteset of those. - id: FoodBakedCookie #should be replaced with gingerbread sheets or something... provided you're willing to make a full spriteset of those.
amount: 5 amount: 5
- type: Bloodstream - type: Bloodstream
bloodReagent: Sugar bloodReagents:
reagents:
- ReagentId: Sugar
Quantity: 1
- ReagentId: Butter
Quantity: 2
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:

View File

@ -33,7 +33,10 @@
- id: FoodMeat - id: FoodMeat
amount: 5 amount: 5
- type: Bloodstream - type: Bloodstream
bloodReagent: InsectBlood bloodReagents:
reagents:
- ReagentId: InsectBlood
Quantity: 1
- type: DamageVisuals - type: DamageVisuals
damageOverlayGroups: damageOverlayGroups:
Brute: Brute:

View File

@ -74,7 +74,10 @@
Burn: Burn:
sprite: Mobs/Effects/burn_damage.rsi sprite: Mobs/Effects/burn_damage.rsi
- type: Bloodstream - type: Bloodstream
bloodReagent: Slime # TODO Color slime blood based on their slime color or smth bloodReagents: # TODO Color slime blood based on their slime color or smth
reagents:
- ReagentId: Slime
Quantity: 1
- type: Barotrauma - type: Barotrauma
damage: damage:
types: types:

View File

@ -89,7 +89,10 @@
Burn: Burn:
sprite: Mobs/Effects/burn_damage.rsi sprite: Mobs/Effects/burn_damage.rsi
- type: Bloodstream - type: Bloodstream
bloodReagent: AmmoniaBlood bloodReagents:
reagents:
- ReagentId: AmmoniaBlood
Quantity: 1
- type: MeleeWeapon - type: MeleeWeapon
soundHit: soundHit:
collection: AlienClaw collection: AlienClaw