Bug fixes for metabolisable reagents (#4385)
* HealthChangeMetabolism now scales with ticktime and metabolism rate Both Food and Drink metabolisms scale with ticktime, Now HealthChangeMetabolism also does so. Additionally, 'healthChange' now correctly scales with the metabolism rate, so it's description is now correct. * LiverBehaviour now uses correct frameTime Previously, the liver only metabolised reagants once every second, but incorrectly passes the current frameTime to the metabilism function, not 1 second. * Stomach now only transfers non-empty solutions. Makes debugging bloodstream bugs easier if the stomach is not constantly adding empty solution to it. * Fixed StomachBehaviour using wrong SolutionContainerComponent Stomach was using the first SolutionContainerComponent in the owner of the body, instead of the container in the owner of the mechanism (stomach). As a result, it used to use the BloodStreamComponent.Solution as a "Stomach". * Update StomachBehavior.cs Somach now checks if it still contains a reagant, before transferring it. * Added argument to IMetabolizable.Metabolize() Added availableReagent argument to IMetabolizable.Metabolize(), This ensures that this function does not over-metabolize a reagant, which can happen if tickTime*metabolismRate is larger than the available reagant * Revert "Stomach now only transfers non-empty solutions." This reverts commit 2a51e2d87e6e17ab76b48e5316ce501ec05ac061. * Renamed _updateInterval to _updateIntervalSeconds Also modified doc comment specifying units * Fix spelling of healthChangeAmount Changed from healthChangeAmmount to healthChangeAmount * Fixed comment comment used to mention _updateInterval, which has been renamed _updateIntervalSeconds * Fixed typo in comment * fixed typos: reagant -> reagent Most typos were just in comments. * Make metabolizable classes inherit from DefaultMetabolizable Also involved changing around IMetabolizable * Renamed variables metabolismAmount -> amountMetabolized * Updated Comments in DefaultMetabolizable Makes it clearer why DefaultMetabolizable works as it does, and that other classes depend on it.
This commit is contained in:
@@ -17,6 +17,11 @@ namespace Content.Server.Body.Behavior
|
|||||||
|
|
||||||
private float _accumulatedFrameTime;
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay time that determines how often to metabolise blood contents (in seconds).
|
||||||
|
/// </summary>
|
||||||
|
private float _updateIntervalSeconds = 1.0f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the liver is functional.
|
/// Whether the liver is functional.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -63,13 +68,13 @@ namespace Content.Server.Body.Behavior
|
|||||||
|
|
||||||
_accumulatedFrameTime += frameTime;
|
_accumulatedFrameTime += frameTime;
|
||||||
|
|
||||||
// Update at most once per second
|
// Update at most once every _updateIntervalSeconds
|
||||||
if (_accumulatedFrameTime < 1)
|
if (_accumulatedFrameTime < _updateIntervalSeconds)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_accumulatedFrameTime -= 1;
|
_accumulatedFrameTime -= _updateIntervalSeconds;
|
||||||
|
|
||||||
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||||
{
|
{
|
||||||
@@ -90,6 +95,10 @@ namespace Content.Server.Body.Behavior
|
|||||||
continue;
|
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'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 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.
|
//TODO BODY Liver failure.
|
||||||
@@ -99,8 +108,9 @@ namespace Content.Server.Body.Behavior
|
|||||||
// Run metabolism code for each reagent
|
// Run metabolism code for each reagent
|
||||||
foreach (var metabolizable in prototype.Metabolism)
|
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);
|
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
|
||||||
|
availableReagent -= reagentDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ namespace Content.Server.Body.Behavior
|
|||||||
/// </param>
|
/// </param>
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Do not metabolise if the organ does not have a body.
|
||||||
if (Body == null)
|
if (Body == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -45,7 +47,9 @@ namespace Content.Server.Body.Behavior
|
|||||||
|
|
||||||
_accumulatedFrameTime -= 1;
|
_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))
|
!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -61,8 +65,19 @@ namespace Content.Server.Body.Behavior
|
|||||||
delta.Increment(1);
|
delta.Increment(1);
|
||||||
if (delta.Lifetime > _digestionDelay)
|
if (delta.Lifetime > _digestionDelay)
|
||||||
{
|
{
|
||||||
solution.TryRemoveReagent(delta.ReagentId, delta.Quantity);
|
// This reagent has been in the somach long enough, TRY to transfer it.
|
||||||
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
|
// 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);
|
_reagentDeltas.Remove(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,10 +148,10 @@ namespace Content.Server.Body.Behavior
|
|||||||
|
|
||||||
public bool TryTransferSolution(Solution solution)
|
public bool TryTransferSolution(Solution solution)
|
||||||
{
|
{
|
||||||
if (Body == null || !CanTransferSolution(solution))
|
if (Owner == null || !CanTransferSolution(solution))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
|
if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
using Content.Shared.Chemistry.Metabolizable;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -9,28 +9,27 @@ namespace Content.Server.Chemistry.Metabolism
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataDefinition]
|
[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
|
//How much thirst is satiated when 1u of the reagent is metabolized
|
||||||
[DataField("hydrationFactor")]
|
[DataField("hydrationFactor")]
|
||||||
public float HydrationFactor { get; set; } = 30.0f;
|
public float HydrationFactor { get; set; } = 30.0f;
|
||||||
|
|
||||||
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
|
//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))
|
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 amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
|
||||||
return metabolismAmount;
|
return amountMetabolized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Chemistry.Metabolizable;
|
using Content.Shared.Chemistry.Metabolizable;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
@@ -9,30 +9,30 @@ namespace Content.Server.Chemistry.Metabolism
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default metabolism for food reagents. Attempts to find a HungerComponent on the target,
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public class DefaultFood : IMetabolizable
|
public class DefaultFood : DefaultMetabolizable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Rate of metabolism in units / second
|
|
||||||
/// </summary>
|
|
||||||
[DataField("rate")] public ReagentUnit MetabolismRate { get; private set; } = ReagentUnit.New(1.0);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much hunger is satiated when 1u of the reagent is metabolized
|
/// How much hunger is satiated when 1u of the reagent is metabolized
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f;
|
[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
|
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
|
||||||
return metabolismAmount;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,11 @@ namespace Content.Server.Chemistry.Metabolism
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public class HealthChangeMetabolism : IMetabolizable
|
public class HealthChangeMetabolism : DefaultMetabolizable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// How much of the reagent should be metabolized each sec.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("rate")]
|
|
||||||
public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much damage is changed when 1u of the reagent is metabolized.
|
/// How much damage is changed when 1u of the reagent is metabolized.
|
||||||
@@ -41,14 +36,23 @@ namespace Content.Server.Chemistry.Metabolism
|
|||||||
/// <param name="solutionEntity"></param>
|
/// <param name="solutionEntity"></param>
|
||||||
/// <param name="reagentId"></param>
|
/// <param name="reagentId"></param>
|
||||||
/// <param name="tickTime"></param>
|
/// <param name="tickTime"></param>
|
||||||
|
/// <param name="availableReagent">Reagent available to be metabolized.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
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))
|
if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
|
||||||
{
|
{
|
||||||
health.ChangeDamage(DamageType, (int)HealthChange, true);
|
// Heal damage by healthChangeAmmount, rounding down to nearest integer
|
||||||
float decHealthChange = (float) (HealthChange - (int) HealthChange);
|
health.ChangeDamage(DamageType, (int) healthChangeAmount, true);
|
||||||
_accumulatedHealth += decHealthChange;
|
|
||||||
|
// Store decimal remainder of healthChangeAmmount in _accumulatedHealth
|
||||||
|
_accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount);
|
||||||
|
|
||||||
if (_accumulatedHealth >= 1)
|
if (_accumulatedHealth >= 1)
|
||||||
{
|
{
|
||||||
@@ -62,7 +66,7 @@ namespace Content.Server.Chemistry.Metabolism
|
|||||||
_accumulatedHealth += 1;
|
_accumulatedHealth += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MetabolismRate;
|
return amountMetabolized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
|
||||||
namespace Content.Shared.Chemistry.Metabolizable
|
namespace Content.Shared.Chemistry.Metabolizable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataDefinition]
|
[DataDefinition]
|
||||||
public class DefaultMetabolizable : IMetabolizable
|
public class DefaultMetabolizable : IMetabolizable
|
||||||
@@ -13,12 +15,22 @@ namespace Content.Shared.Chemistry.Metabolizable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rate of metabolism in units / second
|
/// Rate of metabolism in units / second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("rate")]
|
[DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
|
||||||
public double MetabolismRate { get; set; } = 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Shared.Chemistry.Metabolizable
|
namespace Content.Shared.Chemistry.Metabolizable
|
||||||
@@ -16,7 +16,8 @@ namespace Content.Shared.Chemistry.Metabolizable
|
|||||||
/// <param name="solutionEntity">The entity containing the solution.</param>
|
/// <param name="solutionEntity">The entity containing the solution.</param>
|
||||||
/// <param name="reagentId">The reagent id</param>
|
/// <param name="reagentId">The reagent id</param>
|
||||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
||||||
|
/// <param name="availableReagent">Reagent available to be metabolized.</param>
|
||||||
/// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns>
|
/// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns>
|
||||||
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime);
|
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user