diff --git a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs
new file mode 100644
index 0000000000..e43a1d7dd3
--- /dev/null
+++ b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs
@@ -0,0 +1,41 @@
+using System;
+using Content.Server.GameObjects.Components.Nutrition;
+using Content.Shared.Interfaces.Chemistry;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Serialization;
+using Robust.Shared.Serialization;
+
+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.
+ ///
+ class DefaultDrink : IMetabolizable
+ {
+ //Rate of metabolism in units / second
+ private int _metabolismRate;
+ public int MetabolismRate => _metabolismRate;
+
+ //How much thirst is satiated when 1u of the reagent is metabolized
+ private float _hydrationFactor;
+ public float HydrationFactor => _hydrationFactor;
+
+ void IExposeData.ExposeData(ObjectSerializer serializer)
+ {
+ serializer.DataField(ref _metabolismRate, "rate", 1);
+ serializer.DataField(ref _hydrationFactor, "nutrimentFactor", 30.0f);
+ }
+
+ //Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
+ int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ {
+ int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
+ if (solutionEntity.TryGetComponent(out ThirstComponent thirst))
+ thirst.UpdateThirst(metabolismAmount * HydrationFactor);
+
+ //Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
+ return metabolismAmount;
+ }
+ }
+}
diff --git a/Content.Server/Chemistry/Metabolism/DefaultFood.cs b/Content.Server/Chemistry/Metabolism/DefaultFood.cs
new file mode 100644
index 0000000000..824721acfd
--- /dev/null
+++ b/Content.Server/Chemistry/Metabolism/DefaultFood.cs
@@ -0,0 +1,41 @@
+using System;
+using Content.Server.GameObjects.Components.Nutrition;
+using Content.Shared.Interfaces.Chemistry;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Serialization;
+using Robust.Shared.Serialization;
+
+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.
+ ///
+ class DefaultFood : IMetabolizable
+ {
+ //Rate of metabolism in units / second
+ private int _metabolismRate;
+ public int MetabolismRate => _metabolismRate;
+
+ //How much hunger is satiated when 1u of the reagent is metabolized
+ private float _nutritionFactor;
+ public float NutritionFactor => _nutritionFactor;
+
+ void IExposeData.ExposeData(ObjectSerializer serializer)
+ {
+ serializer.DataField(ref _metabolismRate, "rate", 1);
+ serializer.DataField(ref _nutritionFactor, "nutrimentFactor", 30.0f);
+ }
+
+ //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
+ int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ {
+ int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
+ if (solutionEntity.TryGetComponent(out HungerComponent hunger))
+ hunger.UpdateFood(metabolismAmount * NutritionFactor);
+
+ //Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence
+ return metabolismAmount;
+ }
+ }
+}
diff --git a/Content.Server/Chemistry/ExplosionReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
similarity index 98%
rename from Content.Server/Chemistry/ExplosionReactionEffect.cs
rename to Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
index 4ab6f5e282..7382a7bdc2 100644
--- a/Content.Server/Chemistry/ExplosionReactionEffect.cs
+++ b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
@@ -5,7 +5,7 @@ using Content.Shared.Interfaces;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
-namespace Content.Server.Chemistry
+namespace Content.Server.Chemistry.ReactionEffects
{
class ExplosionReactionEffect : IReactionEffect
{
diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs
index 25e104688d..fb53626e59 100644
--- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs
+++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
@@ -44,9 +44,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/eatfood.ogg");
// Default is transfer 30 units
- serializer.DataField(ref _transferAmount,
- "transfer_amount",
- 30 / StomachComponent.NutrimentFactor);
+ serializer.DataField(ref _transferAmount, "transfer_amount", 5);
// E.g. empty chip packet when done
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
@@ -81,8 +79,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
_initialContents = null;
if (_contents.CurrentVolume == 0)
{
- _contents.TryAddReagent("chem.Nutriment", 30 / StomachComponent.NutrimentFactor,
- out _);
+ _contents.TryAddReagent("chem.Nutriment", 5, out _);
}
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
diff --git a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs b/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs
index 2f4e1933fc..7478d6c038 100644
--- a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs
+++ b/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs
@@ -1,7 +1,11 @@
+using System.Collections.Generic;
using Content.Server.GameObjects.Components.Chemistry;
+using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -10,43 +14,35 @@ namespace Content.Server.GameObjects.Components.Nutrition
[RegisterComponent]
public class StomachComponent : SharedStomachComponent
{
- // Essentially every time it ticks it'll pull out the MetabolisationAmount of reagents and process them.
- // Generic food goes under "nutriment" like SS13
- // There's also separate hunger and thirst components which means you can have a stomach
- // but not require food / water.
- public static readonly int NutrimentFactor = 30;
- public static readonly int HydrationFactor = 30;
- public static readonly int MetabolisationAmount = 5;
+#pragma warning disable 649
+ [Dependency] private readonly IPrototypeManager _prototypeManager;
+#pragma warning restore 649
+ [ViewVariables(VVAccess.ReadOnly)]
private SolutionComponent _stomachContents;
- public float MetaboliseDelay => _metaboliseDelay;
- [ViewVariables]
- private float _metaboliseDelay; // How long between metabolisation for 5 units
-
public int MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
}
-
- private float _metabolisationCounter = 0.0f;
-
private int _initialMaxVolume;
+ //Used to track changes to reagent amounts during metabolism
+ private readonly Dictionary _reagentDeltas = new Dictionary();
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
- serializer.DataField(ref _metaboliseDelay, "metabolise_delay", 6.0f);
serializer.DataField(ref _initialMaxVolume, "max_volume", 20);
}
public override void Initialize()
{
base.Initialize();
- // Shouldn't add to Owner to avoid cross-contamination (e.g. with blood or whatever they made hold other solutions)
+ //Doesn't use Owner.AddComponent<>() to avoid cross-contamination (e.g. with blood or whatever they holds other solutions)
_stomachContents = new SolutionComponent();
_stomachContents.InitializeFromPrototype();
_stomachContents.MaxVolume = _initialMaxVolume;
+ _stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
}
public bool TryTransferSolution(Solution solution)
@@ -61,69 +57,42 @@ namespace Content.Server.GameObjects.Components.Nutrition
}
///
- /// This is where the magic happens. Make people throw up, increase nutrition, whatever
+ /// Loops through each reagent in _stomachContents, and calls the IMetabolizable for each of them./>
///
- ///
- public void React(Solution solution)
+ /// The time since the last metabolism tick in seconds.
+ public void Metabolize(float tickTime)
{
- // TODO: Implement metabolism post from here
- // https://github.com/space-wizards/space-station-14/issues/170#issuecomment-481835623 as raised by moneyl
- var hungerUpdate = 0;
- var thirstUpdate = 0;
- foreach (var reagent in solution.Contents)
+ if (_stomachContents.CurrentVolume == 0)
+ return;
+
+ //Run metabolism for each reagent, track quantity changes
+ _reagentDeltas.Clear();
+ foreach (var reagent in _stomachContents.ReagentList)
{
- switch (reagent.ReagentId)
+ if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
+ continue;
+
+ foreach (var metabolizable in proto.Metabolism)
{
- case "chem.Nutriment":
- hungerUpdate++;
- break;
- case "chem.H2O":
- thirstUpdate++;
- break;
- case "chem.Alcohol":
- thirstUpdate++;
- break;
- default:
- continue;
+ _reagentDeltas[reagent.ReagentId] = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
}
}
- // Quantity x restore amount per unit
- if (hungerUpdate > 0 && Owner.TryGetComponent(out HungerComponent hungerComponent))
+ //Apply changes to quantity afterwards. Can't change the reagent quantities while the iterating the
+ //list of reagents, because that would invalidate the iterator and throw an exception.
+ foreach (var reagentDelta in _reagentDeltas)
{
- hungerComponent.UpdateFood(hungerUpdate * NutrimentFactor);
+ _stomachContents.TryRemoveReagent(reagentDelta.Key, reagentDelta.Value);
}
-
- if (thirstUpdate > 0 && Owner.TryGetComponent(out ThirstComponent thirstComponent))
- {
- thirstComponent.UpdateThirst(thirstUpdate * HydrationFactor);
- }
-
- // TODO: Dispose solution?
}
- public void Metabolise()
+ ///
+ /// Triggers metabolism of the reagents inside _stomachContents. Called by
+ ///
+ /// The time since the last metabolism tick in seconds.
+ public void OnUpdate(float tickTime)
{
- if (_stomachContents.CurrentVolume == 0)
- {
- return;
- }
-
- var metabolisation = _stomachContents.SplitSolution(MetabolisationAmount);
-
- React(metabolisation);
- }
-
- public void OnUpdate(float frameTime)
- {
- _metabolisationCounter += frameTime;
- if (_metabolisationCounter >= MetaboliseDelay)
- {
- // Going to be rounding issues with frametime but no easy way to avoid it with int reagents.
- // It is a long-term mechanic so shouldn't be a big deal.
- Metabolise();
- _metabolisationCounter -= MetaboliseDelay;
- }
+ Metabolize(tickTime);
}
}
}
diff --git a/Content.Server/Interfaces/Chemistry/IReactionEffect.cs b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs
index b3e388bb8a..bd01799557 100644
--- a/Content.Server/Interfaces/Chemistry/IReactionEffect.cs
+++ b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs
@@ -8,6 +8,6 @@ namespace Content.Shared.Interfaces
///
public interface IReactionEffect : IExposeData
{
- void React(IEntity solutionEntity, int intensity );
+ void React(IEntity solutionEntity, int intensity);
}
}
diff --git a/Content.Shared/Chemistry/DefaultMetabolizable.cs b/Content.Shared/Chemistry/DefaultMetabolizable.cs
new file mode 100644
index 0000000000..ab5816d9f0
--- /dev/null
+++ b/Content.Shared/Chemistry/DefaultMetabolizable.cs
@@ -0,0 +1,27 @@
+using System;
+using Content.Shared.Interfaces.Chemistry;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Serialization;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chemistry
+{
+ //Default metabolism for reagents. Metabolizes the reagent with no effects
+ class DefaultMetabolizable : IMetabolizable
+ {
+ //Rate of metabolism in units / second
+ private int _metabolismRate = 1;
+ public int MetabolismRate => _metabolismRate;
+
+ void IExposeData.ExposeData(ObjectSerializer serializer)
+ {
+ serializer.DataField(ref _metabolismRate, "rate", 1);
+ }
+
+ int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
+ {
+ int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
+ return metabolismAmount;
+ }
+ }
+}
diff --git a/Content.Shared/Chemistry/ReagentPrototype.cs b/Content.Shared/Chemistry/ReagentPrototype.cs
index b123489a2b..e4cbfab8f1 100644
--- a/Content.Shared/Chemistry/ReagentPrototype.cs
+++ b/Content.Shared/Chemistry/ReagentPrototype.cs
@@ -1,5 +1,9 @@
-using Robust.Shared.Maths;
+using System;
+using System.Collections.Generic;
+using Content.Shared.Interfaces.Chemistry;
+using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -8,18 +12,28 @@ namespace Content.Shared.Chemistry
[Prototype("reagent")]
public class ReagentPrototype : IPrototype, IIndexedPrototype
{
- public string ID { get; private set; }
- public string Name { get; private set; }
- public string Description { get; private set; }
- public Color SubstanceColor { get; private set; }
+ private string _id;
+ private string _name;
+ private string _description;
+ private Color _substanceColor;
+ private List _metabolism;
+
+ public string ID => _id;
+ public string Name => _name;
+ public string Description => _description;
+ public Color SubstanceColor => _substanceColor;
+ //List of metabolism effects this reagent has, should really only be used server-side.
+ public List Metabolism => _metabolism;
public void LoadFrom(YamlMappingNode mapping)
{
- ID = mapping.GetNode("id").AsString();
- Name = mapping.GetNode("name").ToString();
- Description = mapping.GetNode("desc").ToString();
+ var serializer = YamlObjectSerializer.NewReader(mapping);
- SubstanceColor = mapping.TryGetNode("color", out var colorNode) ? colorNode.AsHexColor(Color.White) : Color.White;
+ serializer.DataField(ref _id, "id", string.Empty);
+ serializer.DataField(ref _name, "name", string.Empty);
+ serializer.DataField(ref _description, "desc", string.Empty);
+ serializer.DataField(ref _substanceColor, "color", Color.White);
+ serializer.DataField(ref _metabolism, "metabolism", new List{new DefaultMetabolizable()});
}
}
}
diff --git a/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs b/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs
new file mode 100644
index 0000000000..4b03ef51e2
--- /dev/null
+++ b/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using Content.Shared.Chemistry;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Serialization;
+
+namespace Content.Shared.Interfaces.Chemistry
+{
+ ///
+ /// Metabolism behavior for a reagent.
+ ///
+ public interface IMetabolizable : IExposeData
+ {
+ ///
+ /// Metabolize the attached reagent. Return the amount of reagent to be removed from the solution.
+ /// You shouldn't remove the reagent yourself to avoid invalidating the iterator of the metabolism
+ /// organ that is processing it's reagents.
+ ///
+ /// The entity containing the solution.
+ /// The reagent id
+ /// The time since the last metabolism tick in seconds.
+ /// The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.
+ int Metabolize(IEntity solutionEntity, string reagentId, float tickTime);
+ }
+}
diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml
index f12ac1cdaa..a5e29b8487 100644
--- a/Resources/Prototypes/Reagents/chemicals.yml
+++ b/Resources/Prototypes/Reagents/chemicals.yml
@@ -2,6 +2,9 @@
id: chem.Nutriment
name: Nutriment
desc: Generic nutrition
+ metabolism:
+ - !type:DefaultFood
+ rate: 1
- type: reagent
id: chem.H2SO4
@@ -12,6 +15,9 @@
id: chem.H2O
name: Water
desc: A tasty colorless liquid.
+ metabolism:
+ - !type:DefaultDrink
+ rate: 1
- type: reagent
id: chem.Ice
diff --git a/Resources/Prototypes/Reagents/drinks.yml b/Resources/Prototypes/Reagents/drinks.yml
index 7f5217b21c..eb804d8d8a 100644
--- a/Resources/Prototypes/Reagents/drinks.yml
+++ b/Resources/Prototypes/Reagents/drinks.yml
@@ -17,13 +17,22 @@
id: chem.Cola
name: Cola
desc: A sweet, carbonated soft drink. Caffeine free.
+ metabolism:
+ - !type:DefaultDrink
+ rate: 1
- type: reagent
id: chem.Coffee
name: Coffee
desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine.
+ metabolism:
+ - !type:DefaultDrink
+ rate: 1
- type: reagent
id: chem.Tea
name: Tea
- desc: A made by boiling leaves of the tea tree, Camellia sinensis.
\ No newline at end of file
+ desc: A made by boiling leaves of the tea tree, Camellia sinensis.
+ metabolism:
+ - !type:DefaultDrink
+ rate: 1