diff --git a/Content.Server/Body/Behavior/LiverBehavior.cs b/Content.Server/Body/Behavior/LiverBehavior.cs
index 82e12b29ce..95897b320a 100644
--- a/Content.Server/Body/Behavior/LiverBehavior.cs
+++ b/Content.Server/Body/Behavior/LiverBehavior.cs
@@ -17,6 +17,11 @@ namespace Content.Server.Body.Behavior
private float _accumulatedFrameTime;
+ ///
+ /// Delay time that determines how often to metabolise blood contents (in seconds).
+ ///
+ private float _updateIntervalSeconds = 1.0f;
+
///
/// Whether the liver is functional.
///
@@ -63,13 +68,13 @@ namespace Content.Server.Body.Behavior
_accumulatedFrameTime += frameTime;
- // Update at most once per second
- if (_accumulatedFrameTime < 1)
+ // Update at most once every _updateIntervalSeconds
+ if (_accumulatedFrameTime < _updateIntervalSeconds)
{
return;
}
- _accumulatedFrameTime -= 1;
+ _accumulatedFrameTime -= _updateIntervalSeconds;
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
@@ -90,6 +95,10 @@ namespace Content.Server.Body.Behavior
continue;
}
+ // How much reagent is available to metabolise?
+ // This needs to be passed to other functions that have metabolism rate information, such that they don't "overmetabolise" a reagent.
+ var availableReagent = bloodstream.Solution.Solution.GetReagentQuantity(reagent.ReagentId);
+
//TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality
//TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10
//TODO BODY Liver failure.
@@ -99,8 +108,9 @@ namespace Content.Server.Body.Behavior
// Run metabolism code for each reagent
foreach (var metabolizable in prototype.Metabolism)
{
- var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, frameTime);
+ var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent);
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
+ availableReagent -= reagentDelta;
}
}
}
diff --git a/Content.Server/Body/Behavior/StomachBehavior.cs b/Content.Server/Body/Behavior/StomachBehavior.cs
index a6c903efe9..813b8a3a5e 100644
--- a/Content.Server/Body/Behavior/StomachBehavior.cs
+++ b/Content.Server/Body/Behavior/StomachBehavior.cs
@@ -30,6 +30,8 @@ namespace Content.Server.Body.Behavior
///
public override void Update(float frameTime)
{
+
+ // Do not metabolise if the organ does not have a body.
if (Body == null)
{
return;
@@ -45,7 +47,9 @@ namespace Content.Server.Body.Behavior
_accumulatedFrameTime -= 1;
- if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solution) ||
+ // Note that "Owner" should be the organ that has this behaviour/mechanism, and it should have a dedicated
+ // solution container. "Body.Owner" is something else, and may have more than one solution container.
+ if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) ||
!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
@@ -61,8 +65,19 @@ namespace Content.Server.Body.Behavior
delta.Increment(1);
if (delta.Lifetime > _digestionDelay)
{
- solution.TryRemoveReagent(delta.ReagentId, delta.Quantity);
- transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
+ // This reagent has been in the somach long enough, TRY to transfer it.
+ // But first, check if the reagent still exists, and how much is left.
+ // Some poor spessman may have washed down a potassium snack with some water.
+ if (solution.Solution.ContainsReagent(delta.ReagentId, out ReagentUnit quantity)){
+
+ if (quantity > delta.Quantity) {
+ quantity = delta.Quantity;
+ }
+
+ solution.TryRemoveReagent(delta.ReagentId, quantity);
+ transferSolution.AddReagent(delta.ReagentId, quantity);
+ }
+
_reagentDeltas.Remove(delta);
}
}
@@ -133,10 +148,10 @@ namespace Content.Server.Body.Behavior
public bool TryTransferSolution(Solution solution)
{
- if (Body == null || !CanTransferSolution(solution))
+ if (Owner == null || !CanTransferSolution(solution))
return false;
- if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
+ if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
{
return false;
}
diff --git a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs
index 3b32776c85..255200f103 100644
--- a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs
+++ b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs
@@ -1,4 +1,4 @@
-using Content.Server.Nutrition.Components;
+using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
@@ -9,28 +9,27 @@ namespace Content.Server.Chemistry.Metabolism
{
///
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
- /// and to update it's thirst values.
+ /// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable.
///
[DataDefinition]
- public class DefaultDrink : IMetabolizable
+ public class DefaultDrink : DefaultMetabolizable
{
- //Rate of metabolism in units / second
- [DataField("rate")]
- public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
-
//How much thirst is satiated when 1u of the reagent is metabolized
[DataField("hydrationFactor")]
public float HydrationFactor { get; set; } = 30.0f;
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
- ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{
- var metabolismAmount = MetabolismRate * tickTime;
+ // use DefaultMetabolism to determine how much reagent we should metabolize
+ var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
+
+ // If metabolizing entity has a ThirstComponent, hydrate them.
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
- thirst.UpdateThirst(metabolismAmount.Float() * HydrationFactor);
+ thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor);
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
- return metabolismAmount;
+ return amountMetabolized;
}
}
}
diff --git a/Content.Server/Chemistry/Metabolism/DefaultFood.cs b/Content.Server/Chemistry/Metabolism/DefaultFood.cs
index aeba8ed95d..fafcf58dd1 100644
--- a/Content.Server/Chemistry/Metabolism/DefaultFood.cs
+++ b/Content.Server/Chemistry/Metabolism/DefaultFood.cs
@@ -1,4 +1,4 @@
-using Content.Server.Nutrition.Components;
+using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
@@ -9,30 +9,30 @@ namespace Content.Server.Chemistry.Metabolism
{
///
/// Default metabolism for food reagents. Attempts to find a HungerComponent on the target,
- /// and to update it's hunger values.
+ /// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable.
///
[DataDefinition]
- public class DefaultFood : IMetabolizable
+ public class DefaultFood : DefaultMetabolizable
{
- ///
- /// Rate of metabolism in units / second
- ///
- [DataField("rate")] public ReagentUnit MetabolismRate { get; private set; } = ReagentUnit.New(1.0);
///
/// How much hunger is satiated when 1u of the reagent is metabolized
///
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f;
- //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
- ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
- {
- var metabolismAmount = MetabolismRate * tickTime;
- if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
- hunger.UpdateFood(metabolismAmount.Float() * NutritionFactor);
- //Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence
- return metabolismAmount;
+ //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
+ public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
+ {
+ // use DefaultMetabolism to determine how much reagent we should metabolize
+ var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
+
+ // If metabolizing entity has a HungerComponent, feed them.
+ if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
+ hunger.UpdateFood(amountMetabolized.Float() * NutritionFactor);
+
+ //Return amount of reagent to be removed. Reagent is removed regardless of HungerComponent presence
+ return amountMetabolized;
}
}
}
diff --git a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs b/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs
index 45bbff68d7..bbf2534505 100644
--- a/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs
+++ b/Content.Server/Chemistry/Metabolism/HealthChangeMetabolism.cs
@@ -10,16 +10,11 @@ namespace Content.Server.Chemistry.Metabolism
{
///
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
- /// and to update its damage values.
+ /// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable.
///
[DataDefinition]
- public class HealthChangeMetabolism : IMetabolizable
+ public class HealthChangeMetabolism : DefaultMetabolizable
{
- ///
- /// How much of the reagent should be metabolized each sec.
- ///
- [DataField("rate")]
- public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
///
/// How much damage is changed when 1u of the reagent is metabolized.
@@ -41,14 +36,23 @@ namespace Content.Server.Chemistry.Metabolism
///
///
///
+ /// Reagent available to be metabolized.
///
- ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{
+ // use DefaultMetabolism to determine how much reagent we should metabolize
+ var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
+
+ // how much does this much reagent heal for
+ var healthChangeAmount = HealthChange * amountMetabolized.Float();
+
if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
{
- health.ChangeDamage(DamageType, (int)HealthChange, true);
- float decHealthChange = (float) (HealthChange - (int) HealthChange);
- _accumulatedHealth += decHealthChange;
+ // Heal damage by healthChangeAmmount, rounding down to nearest integer
+ health.ChangeDamage(DamageType, (int) healthChangeAmount, true);
+
+ // Store decimal remainder of healthChangeAmmount in _accumulatedHealth
+ _accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount);
if (_accumulatedHealth >= 1)
{
@@ -62,7 +66,7 @@ namespace Content.Server.Chemistry.Metabolism
_accumulatedHealth += 1;
}
}
- return MetabolismRate;
+ return amountMetabolized;
}
}
}
diff --git a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs
index cbd10016a5..939acdd21a 100644
--- a/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs
+++ b/Content.Shared/Chemistry/Metabolizable/DefaultMetabolizable.cs
@@ -1,11 +1,13 @@
-using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Metabolizable
{
///
- /// Default metabolism for reagents. Metabolizes the reagent with no effects
+ /// Default metabolization for reagents. Returns the amount of reagents metabolized without applying effects.
+ /// Metabolizes reagents at a constant rate, limited by how much is available. Other classes are derived from
+ /// this class, so that they do not need their own metabolization quantity calculation.
///
[DataDefinition]
public class DefaultMetabolizable : IMetabolizable
@@ -13,12 +15,22 @@ namespace Content.Shared.Chemistry.Metabolizable
///
/// Rate of metabolism in units / second
///
- [DataField("rate")]
- public double MetabolismRate { get; set; } = 1;
+ [DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
- ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{
- return ReagentUnit.New(MetabolismRate * tickTime);
+
+ // How much reagent should we metabolize
+ // The default behaviour is to metabolize at a constant rate, independent of the quantity of reagents.
+ var amountMetabolized = MetabolismRate * tickTime;
+
+ // is that much reagent actually available?
+ if (availableReagent < amountMetabolized)
+ {
+ return availableReagent;
+ }
+
+ return amountMetabolized;
}
}
}
diff --git a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs b/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs
index e08e209a0c..f8b2a888cc 100644
--- a/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs
+++ b/Content.Shared/Chemistry/Metabolizable/IMetabolizable.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.Metabolizable
@@ -16,7 +16,8 @@ namespace Content.Shared.Chemistry.Metabolizable
/// The entity containing the solution.
/// The reagent id
/// The time since the last metabolism tick in seconds.
+ /// Reagent available to be metabolized.
/// The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.
- ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime);
+ ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent);
}
}