Metabolism refactor (#4395)

* metabolism -> respirator, add reageanteffect and reagenteffectcondition, start on metabolizercomp/system

* move LiverBehavior metabolism logic to Metabolizer

* minor tweaks and update all YAML

* how about actually taking conditions into account

* off by one

* removals

* reviews
This commit is contained in:
mirrorcult
2021-07-31 04:50:32 -07:00
committed by GitHub
parent dc18997bf8
commit 8aa4f998de
36 changed files with 572 additions and 558 deletions

View File

@@ -154,7 +154,8 @@ namespace Content.Client.Entry
"GasPassiveGate",
"GasValve",
"GasThermoMachine",
"Metabolism",
"Respirator",
"Metabolizer",
"AiFactionTag",
"PressureProtection",
"AMEPart",

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Content.Server.Atmos;
using Content.Server.Body.Behavior;
using Content.Server.Body.Circulatory;
using Content.Server.Metabolism;
using Content.Server.Body.Respiratory;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using NUnit.Framework;
@@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests.Body
template: HumanoidTemplate
preset: HumanPreset
centerSlot: torso
- type: Metabolism
- type: Respirator
metabolismHeat: 5000
radiatedHeat: 400
implicitHeatRegulation: 5000
@@ -148,7 +148,7 @@ namespace Content.IntegrationTests.Tests.Body
MapId mapId;
IMapGrid grid = null;
MetabolismComponent metabolism = null;
RespiratorComponent respirator = null;
IEntity human = null;
var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml";
@@ -169,8 +169,8 @@ namespace Content.IntegrationTests.Tests.Body
Assert.True(human.TryGetComponent(out SharedBodyComponent body));
Assert.True(body.HasMechanismBehavior<LungBehavior>());
Assert.True(human.TryGetComponent(out metabolism));
Assert.False(metabolism.Suffocating);
Assert.True(human.TryGetComponent(out respirator));
Assert.False(respirator.Suffocating);
});
var increment = 10;
@@ -178,7 +178,7 @@ namespace Content.IntegrationTests.Tests.Body
for (var tick = 0; tick < 600; tick += increment)
{
await server.WaitRunTicks(increment);
Assert.False(metabolism.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
Assert.False(respirator.Suffocating, $"Entity {human.Name} is suffocating on tick {tick}");
}
await server.WaitIdleAsync();

View File

@@ -1,7 +1,6 @@
using System.Linq;
using Content.Server.Body.Circulatory;
using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -13,52 +12,8 @@ namespace Content.Server.Body.Behavior
/// </summary>
public class LiverBehavior : MechanismBehavior
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private float _accumulatedFrameTime;
/// <summary>
/// Delay time that determines how often to metabolise blood contents (in seconds).
/// </summary>
private float _updateIntervalSeconds = 1.0f;
/// <summary>
/// Whether the liver is functional.
/// </summary>
//[ViewVariables] private bool _liverFailing = false;
/// <summary>
/// Modifier for alcohol damage.
/// </summary>
//[DataField("alcoholLethality")]
//[ViewVariables] private float _alcoholLethality = 0.005f;
/// <summary>
/// Modifier for alcohol damage.
/// </summary>
//[DataField("alcoholExponent")]
//[ViewVariables] private float _alcoholExponent = 1.6f;
/// <summary>
/// Toxin volume that can be purged without damage.
/// </summary>
//[DataField("toxinTolerance")]
//[ViewVariables] private float _toxinTolerance = 3f;
/// <summary>
/// Toxin damage modifier.
/// </summary>
//[DataField("toxinLethality")]
//[ViewVariables] private float _toxinLethality = 0.01f;
/// <summary>
/// Loops through each reagent in _internalSolution,
/// and calls <see cref="IMetabolizable.Metabolize"/> for each of them.
/// Also handles toxins and alcohol.
/// </summary>
/// <param name="frameTime">
/// The time since the last update in seconds.
/// </param>
public override void Update(float frameTime)
{
if (Body == null)
@@ -68,51 +23,13 @@ namespace Content.Server.Body.Behavior
_accumulatedFrameTime += frameTime;
// Update at most once every _updateIntervalSeconds
if (_accumulatedFrameTime < _updateIntervalSeconds)
// Update at most once per second
if (_accumulatedFrameTime < 1)
{
return;
}
_accumulatedFrameTime -= _updateIntervalSeconds;
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
}
if (bloodstream.Solution.CurrentVolume <= ReagentUnit.Zero)
{
return;
}
// Run metabolism for each reagent, remove metabolized reagents
// Using ToList here lets us edit reagents while iterating
foreach (var reagent in bloodstream.Solution.ReagentList.ToList())
{
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? prototype))
{
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.
//TODO Make sure reagent prototypes actually have the toxin and boozepower vars set.
// Run metabolism code for each reagent
foreach (var metabolizable in prototype.Metabolism)
{
var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent);
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
availableReagent -= reagentDelta;
}
}
_accumulatedFrameTime -= 1;
}
}
}

View File

@@ -2,8 +2,8 @@ using System;
using System.Linq;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Respiratory;
using Content.Server.Chemistry.Components;
using Content.Server.Metabolism;
using Content.Shared.Atmos;
using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Reagent;
@@ -72,7 +72,7 @@ namespace Content.Server.Body.Circulatory
{
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
if (!Owner.TryGetComponent(out MetabolismComponent? metabolism))
if (!Owner.TryGetComponent(out RespiratorComponent? metabolism))
{
atmosphereSystem.Merge(to, Air);
Air.Clear();

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Body.Metabolism
{
/// <summary>
/// Handles metabolizing various reagents with given effects.
/// </summary>
[RegisterComponent]
public class MetabolizerComponent : Component
{
public override string Name => "Metabolizer";
public float AccumulatedFrametime = 0.0f;
/// <summary>
/// How often to metabolize reagents, in seconds.
/// </summary>
/// <returns></returns>
[DataField("updateFrequency")]
public float UpdateFrequency = 1.0f;
/// <summary>
/// Whether this metabolizer should attempt to metabolize chemicals in its parent bodies' bloodstream,
/// as opposed to a solution container on the metabolizing entity itself.
/// </summary>
[DataField("takeFromBloodstream")]
public bool TakeFromBloodstream = true;
/// <summary>
/// A dictionary mapping reagent string IDs to a list of effects & associated metabolism rate.
/// </summary>
/// <returns></returns>
[DataField("metabolisms", required: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer<ReagentEffectsEntry, ReagentPrototype>))]
public Dictionary<string, ReagentEffectsEntry> Metabolisms = default!;
}
[DataDefinition]
public class ReagentEffectsEntry
{
/// <summary>
/// Amount of reagent to metabolize, per metabolism cycle.
/// </summary>
[DataField("metabolismRate")]
public ReagentUnit MetabolismRate = ReagentUnit.New(1.0f);
/// <summary>
/// A list of effects to apply when these reagents are metabolized.
/// </summary>
[DataField("effects", required: true)]
public ReagentEffect[] Effects = default!;
}
}

View File

@@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Body.Circulatory;
using Content.Server.Chemistry.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Mechanism;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.Body.Metabolism
{
// TODO mirror in the future working on mechanisms move updating here to BodySystem so it can be ordered?
public class MetabolizerSystem : EntitySystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var metab in ComponentManager.EntityQuery<MetabolizerComponent>(false))
{
metab.AccumulatedFrametime += frameTime;
// Only update as frequently as it should
if (metab.AccumulatedFrametime >= metab.UpdateFrequency)
{
metab.AccumulatedFrametime = 0.0f;
TryMetabolize(metab);
}
}
}
private void TryMetabolize(MetabolizerComponent comp)
{
var owner = comp.Owner;
var reagentList = new List<Solution.ReagentQuantity>();
SolutionContainerComponent? solution = null;
SharedBodyComponent? body = null;
// if this field is passed we should try and take from the bloodstream over anything else
if (comp.TakeFromBloodstream && owner.TryGetComponent<SharedMechanismComponent>(out var mech))
{
body = mech.Body;
if (body != null)
{
if (body.Owner.TryGetComponent<BloodstreamComponent>(out var bloodstream)
&& bloodstream.Solution.CurrentVolume >= ReagentUnit.Zero)
{
solution = bloodstream.Solution;
reagentList = bloodstream.Solution.ReagentList.ToList();
}
}
}
else if (owner.TryGetComponent<SolutionContainerComponent>(out var sol))
{
// if we have no mechanism/body but a solution container instead,
// we'll just use that to metabolize from
solution = sol;
reagentList = sol.ReagentList.ToList();
}
if (solution == null || reagentList.Count == 0)
{
// We're all outta ideas on where to metabolize from
return;
}
// Run metabolism for each reagent, remove metabolized reagents
foreach (var reagent in reagentList)
{
if (!comp.Metabolisms.ContainsKey(reagent.ReagentId))
continue;
var metabolism = comp.Metabolisms[reagent.ReagentId];
// Run metabolism code for each reagent
foreach (var effect in metabolism.Effects)
{
var ent = body != null ? body.Owner : owner;
var conditionsMet = true;
if (effect.Conditions != null)
{
// yes this is 3 nested for loops, but all of these lists are
// basically guaranteed to be small or empty
foreach (var condition in effect.Conditions)
{
if (!condition.Condition(ent, reagent))
{
conditionsMet = false;
break;
}
}
}
if (!conditionsMet)
return;
// If we're part of a body, pass that entity to Metabolize
// Otherwise, just pass our owner entity, maybe we're a plant or something
effect.Metabolize(ent, reagent);
}
solution.TryRemoveReagent(reagent.ReagentId, metabolism.MetabolismRate);
}
}
}
}

View File

@@ -13,7 +13,6 @@ using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Metabolism.Events;
using Content.Shared.MobState;
using Content.Shared.Notification.Managers;
using Robust.Shared.GameObjects;
@@ -21,14 +20,14 @@ using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Metabolism
namespace Content.Server.Body.Respiratory
{
[RegisterComponent]
public class MetabolismComponent : Component
public class RespiratorComponent : Component
{
[ComponentDependency] private readonly SharedBodyComponent? _body = default!;
public override string Name => "Metabolism";
public override string Name => "Respirator";
private float _accumulatedFrameTime;

View File

@@ -1,18 +1,18 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Metabolism
namespace Content.Server.Body.Respiratory
{
[UsedImplicitly]
public class MetabolismSystem : EntitySystem
public class RespiratorSystem : EntitySystem
{
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var metabolism in ComponentManager.EntityQuery<MetabolismComponent>(true))
foreach (var respirator in ComponentManager.EntityQuery<RespiratorComponent>(false))
{
metabolism.Update(frameTime);
respirator.Update(frameTime);
}
}
}

View File

@@ -1,35 +0,0 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Chemistry.Metabolism
{
/// <summary>
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
/// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary>
[DataDefinition]
public class DefaultDrink : DefaultMetabolizable
{
//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
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 ThirstComponent, hydrate them.
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor);
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
return amountMetabolized;
}
}
}

View File

@@ -1,38 +0,0 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Chemistry.Metabolism
{
/// <summary>
/// Default metabolism for food reagents. Attempts to find a HungerComponent on the target,
/// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary>
[DataDefinition]
public class DefaultFood : DefaultMetabolizable
{
/// <summary>
/// How much hunger is satiated when 1u of the reagent is metabolized
/// </summary>
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f;
//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;
}
}
}

View File

@@ -1,72 +0,0 @@
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
namespace Content.Server.Chemistry.Metabolism
{
/// <summary>
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
/// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary>
[DataDefinition]
public class HealthChangeMetabolism : DefaultMetabolizable
{
/// <summary>
/// How much damage is changed when 1u of the reagent is metabolized.
/// </summary>
[DataField("healthChange")]
public float HealthChange { get; set; } = 1.0f;
/// <summary>
/// Class of damage changed, Brute, Burn, Toxin, Airloss.
/// </summary>
[DataField("damageClass")]
public DamageClass DamageType { get; set; } = DamageClass.Brute;
private float _accumulatedHealth;
/// <summary>
/// Remove reagent at set rate, changes damage if a DamageableComponent can be found.
/// </summary>
/// <param name="solutionEntity"></param>
/// <param name="reagentId"></param>
/// <param name="tickTime"></param>
/// <param name="availableReagent">Reagent available to be metabolized.</param>
/// <returns></returns>
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))
{
// 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)
{
health.ChangeDamage(DamageType, 1, true);
_accumulatedHealth -= 1;
}
else if(_accumulatedHealth <= -1)
{
health.ChangeDamage(DamageType, -1, true);
_accumulatedHealth += 1;
}
}
return amountMetabolized;
}
}
}

View File

@@ -0,0 +1,26 @@
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <summary>
/// Used for implementing reagent effects that require a certain amount of reagent before it should be applied.
/// For instance, overdoses.
/// </summary>
public class ReagentThreshold : ReagentEffectCondition
{
[DataField("min")]
public ReagentUnit Min = ReagentUnit.Zero;
[DataField("max")]
public ReagentUnit Max = ReagentUnit.MaxValue;
public override bool Condition(IEntity solutionEntity, Solution.ReagentQuantity reagent)
{
return reagent.Quantity >= Min && reagent.Quantity < Max;
}
}
}

View File

@@ -0,0 +1,56 @@
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
/// and to update its damage values.
/// </summary>
public class HealthChange : ReagentEffect
{
/// <summary>
/// How much damage is changed when 1u of the reagent is metabolized.
/// </summary>
[DataField("healthChange")]
public float AmountToChange { get; set; } = 1.0f;
/// <summary>
/// Class of damage changed, Brute, Burn, Toxin, Airloss.
/// </summary>
[DataField("damageClass")]
public DamageClass DamageType { get; set; } = DamageClass.Brute;
private float _accumulatedHealth;
/// <summary>
/// Changes damage if a DamageableComponent can be found.
/// </summary>
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
{
if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
{
health.ChangeDamage(DamageType, (int)AmountToChange, true);
float decHealthChange = (float) (AmountToChange - (int) AmountToChange);
_accumulatedHealth += decHealthChange;
if (_accumulatedHealth >= 1)
{
health.ChangeDamage(DamageType, 1, true);
_accumulatedHealth -= 1;
}
else if(_accumulatedHealth <= -1)
{
health.ChangeDamage(DamageType, -1, true);
_accumulatedHealth += 1;
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Attempts to find a HungerComponent on the target,
/// and to update it's hunger values.
/// </summary>
public class SatiateHunger : ReagentEffect
{
/// <summary>
/// How much hunger is satiated when 1u of the reagent is metabolized
/// </summary>
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 3.0f;
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
{
if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
hunger.UpdateFood(NutritionFactor);
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
/// and to update it's thirst values.
/// </summary>
public class SatiateThirst : ReagentEffect
{
/// How much thirst is satiated each metabolism tick. Not currently tied to
/// rate or anything.
[DataField("hydrationFactor")]
public float HydrationFactor { get; set; } = 3.0f;
/// Satiate thirst if a ThirstComponent can be found
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
{
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
thirst.UpdateThirst(HydrationFactor);
}
}
}

View File

@@ -4,7 +4,7 @@ using Content.Shared.Emoting;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Metabolism.Events;
using Content.Shared.Body.Metabolism;
using Content.Shared.Movement;
using Content.Shared.Speech;
using Content.Shared.Throwing;

View File

@@ -1,6 +1,6 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.Metabolism.Events
namespace Content.Shared.Body.Metabolism
{
public class ShiverAttemptEvent : CancellableEntityEventArgs
{

View File

@@ -1,6 +1,6 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.Metabolism.Events
namespace Content.Shared.Body.Metabolism
{
public class SweatAttemptEvent : CancellableEntityEventArgs
{

View File

@@ -1,36 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Metabolizable
{
/// <summary>
/// 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>
[DataDefinition]
public class DefaultMetabolizable : IMetabolizable
{
/// <summary>
/// Rate of metabolism in units / second
/// </summary>
[DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{
// 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;
}
}
}

View File

@@ -1,23 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.Metabolizable
{
/// <summary>
/// Metabolism behavior for a reagent.
/// </summary>
public interface IMetabolizable
{
/// <summary>
/// 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.
/// </summary>
/// <param name="solutionEntity">The entity containing the solution.</param>
/// <param name="reagentId">The reagent id</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>
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent);
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Reagent
{
/// <summary>
/// Reagent effects describe behavior that occurs when a reagent is ingested and metabolized by some
/// organ. They only trigger when their conditions (<see cref="ReagentEffectCondition"/>
/// </summary>
[ImplicitDataDefinitionForInheritors]
public abstract class ReagentEffect
{
/// <summary>
/// The list of conditions required for the effect to activate. Not required.
/// </summary>
[DataField("conditions")]
public ReagentEffectCondition[]? Conditions;
public abstract void Metabolize(IEntity solutionEntity, Solution.Solution.ReagentQuantity amount);
}
}

View File

@@ -0,0 +1,13 @@
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Reagent
{
[ImplicitDataDefinitionForInheritors]
public abstract class ReagentEffectCondition
{
public abstract bool Condition(IEntity solutionEntity, Solution.Solution.ReagentQuantity reagent);
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Content.Shared.Botany;
using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reaction;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -16,9 +15,6 @@ namespace Content.Shared.Chemistry.Reagent
[DataDefinition]
public class ReagentPrototype : IPrototype
{
[DataField("metabolism", serverOnly: true)]
private readonly List<IMetabolizable> _metabolism = new() {new DefaultMetabolizable()};
[DataField("tileReactions", serverOnly: true)]
private readonly List<ITileReaction> _tileReactions = new(0);
@@ -60,7 +56,6 @@ namespace Content.Shared.Chemistry.Reagent
public string SpriteReplacementPath { get; } = string.Empty;
//List of metabolism effects this reagent has, should really only be used server-side.
public IReadOnlyList<IMetabolizable> Metabolism => _metabolism;
public IReadOnlyList<ITileReaction> TileReactions => _tileReactions;
public IReadOnlyList<IPlantMetabolizable> PlantMetabolism => _plantMetabolism;

View File

@@ -50,18 +50,30 @@
compatibility: Biological
- type: entity
id: OrganHumanHeart
id: OrganHumanTongue
parent: BaseHumanOrgan
name: heart
description: "I feel bad for the heartless bastard who lost this."
name: tongue
description: "A fleshy muscle mostly used for lying."
components:
- type: Sprite
state: heart-on
state: tongue
- type: Mechanism
size: 1
compatibility: Biological
- type: entity
id: OrganHumanAppendix
parent: BaseHumanOrgan
name: appendix
components:
- type: Sprite
layers:
- state: appendix
- state: appendix-inflamed
visible: false
- type: Mechanism
size: 1
compatibility: Biological
behaviors:
- !type:HeartBehavior {}
- type: entity
id: OrganHumanEars
@@ -91,6 +103,92 @@
behaviors:
- !type:LungBehavior {}
- type: entity
id: OrganHumanHeart
parent: BaseHumanOrgan
name: heart
description: "I feel bad for the heartless bastard who lost this."
components:
- type: Sprite
state: heart-on
- type: Mechanism
size: 1
compatibility: Biological
behaviors:
- !type:HeartBehavior {}
# The heart 'metabolizes' medicines and poisons that aren't filtered out by other organs.
# This is done because these chemicals need to have some effect even if they aren't being filtered out of your body.
# You're technically 'immune to poison' without a heart, but.. uhh, you'll have bigger problems on your hands.
- type: Metabolizer
metabolisms:
Dylovene:
effects:
- !type:HealthChange
damageClass: Toxin
healthChange: -1
Arithrazine:
effects:
- !type:HealthChange
damageClass: Toxin
healthChange: -1
- !type:HealthChange
damageClass: Brute
healthChange: 0.5
Bicaridine:
effects:
- !type:HealthChange
damageClass: Brute
healthChange: -2
Dermaline:
effects:
- !type:HealthChange
damageClass: Burn
healthChange: -3
Dexalin:
effects:
- !type:HealthChange
damageClass: Airloss
healthChange: -1
DexalinPlus:
effects:
- !type:HealthChange
damageClass: Airloss
healthChange: -3
Kelotane:
effects:
- !type:HealthChange
damageClass: Burn
healthChange: -1
Synaptizine:
effects:
- !type:HealthChange
damageClass: Toxin
healthChange: 0.5
HeartbreakerToxin:
effects:
- !type:HealthChange
damageClass: Airloss
healthChange: 4
Lexorin:
effects:
- !type:HealthChange
damageClass: Airloss
healthChange: 7
Omnizine:
effects:
- !type:HealthChange
healthChange: -2
damageClass: Burn
- !type:HealthChange
healthChange: -2
damageClass: Toxin
- !type:HealthChange
healthChange: -2
damageClass: Airloss
- !type:HealthChange
healthChange: -2
damageClass: Brute
- type: entity
id: OrganHumanStomach
parent: BaseHumanOrgan
@@ -108,6 +206,93 @@
digestionDelay: 20
- type: SolutionContainer
maxVol: 250
# The stomach metabolizes stuff like foods and drinks.
# TODO: Have it work off of the ent's solution container, and move this
# to intestines instead.
- type: Metabolizer # Release me from this hell called 1 reagent for every drink
# TODO please make every drink their own base thing
metabolisms:
Flour:
effects:
- !type:SatiateHunger
JuiceApple:
effects:
- !type:SatiateThirst
JuiceBerry:
effects:
- !type:SatiateThirst
JuiceBanana:
effects:
- !type:SatiateThirst
JuiceCarrot:
effects:
- !type:SatiateThirst
JuiceLime:
effects:
- !type:SatiateThirst
JuiceLemon:
effects:
- !type:SatiateThirst
JuiceGrape:
effects:
- !type:SatiateThirst
JuiceOrange:
effects:
- !type:SatiateThirst
JuiceTomato:
effects:
- !type:SatiateThirst
JuiceBerryPoison:
effects:
- !type:SatiateThirst
- !type:HealthChange
damageClass: Toxin
healthChange: 1
JuiceWatermelon:
effects:
- !type:SatiateThirst
JuicePineapple:
effects:
- !type:SatiateThirst
Nutriment:
effects:
- !type:SatiateHunger
Water:
effects:
- !type:SatiateThirst
hydrationFactor: 2
Coffee:
effects:
- !type:SatiateThirst
Tea:
effects:
- !type:SatiateThirst
Milk:
effects:
- !type:SatiateThirst
SpoiledMilk:
effects:
- !type:SatiateThirst
hydrationFactor: -2
MilkSoy:
effects:
- !type:SatiateThirst
hydrationFactor: 2 # soyboys stay winning
MilkOat:
effects:
- !type:SatiateThirst
hydrationFactor: 2 # oatboys stay winning
Cola:
effects:
- !type:SatiateThirst
hydrationFactor: 0.5 # sodaboys stay losing
ThirteenLoko:
effects:
- !type:SatiateThirst
hydrationFactor: 2
- !type:HealthChange
damageClass: Toxin
healthChange: 1
- type: entity
id: OrganHumanLiver
@@ -120,12 +305,17 @@
- type: Mechanism
size: 1
compatibility: Biological
behaviors:
- !type:LiverBehavior
alcoholLethality: 0.005
alcoholExponent: 1.6
toxinTolerance: 3
toxinLethality: 0.01
- type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol.
metabolisms: # TODO add the rest of alcohol
CreamyDelight:
effects:
- !type:SatiateThirst
Lean:
effects:
- !type:SatiateThirst
LeanShine: # who added this?
effects:
- !type:SatiateThirst
- type: entity
id: OrganHumanKidneys
@@ -141,28 +331,4 @@
size: 1
compatibility: Biological
- type: entity
id: OrganHumanTongue
parent: BaseHumanOrgan
name: tongue
description: "A fleshy muscle mostly used for lying."
components:
- type: Sprite
state: tongue
- type: Mechanism
size: 1
compatibility: Biological
- type: entity
id: OrganHumanAppendix
parent: BaseHumanOrgan
name: appendix
components:
- type: Sprite
layers:
- state: appendix
- state: appendix-inflamed
visible: false
- type: Mechanism
size: 1
compatibility: Biological

View File

@@ -67,7 +67,7 @@
currentTemperature: 310.15
specificHeat: 42
tempDamageCoefficient: 0.1
- type: Metabolism
- type: Respirator
metabolismHeat: 5000
radiatedHeat: 400
implicitHeatRegulation: 5000

View File

@@ -38,7 +38,7 @@
0: !type:NormalMobState {}
150: !type:CriticalMobState {}
200: !type:DeadMobState {}
- type: Metabolism
- type: Respirator
- type: UnarmedCombat
range: 1.5
arcwidth: 0

View File

@@ -167,7 +167,7 @@
preset: HumanPreset
- type: Damageable
damageContainer: biologicalDamageContainer
- type: Metabolism
- type: Respirator
metabolismHeat: 5000
radiatedHeat: 400
implicitHeatRegulation: 5000

View File

@@ -84,7 +84,7 @@
- type: Body
template: HumanoidTemplate
preset: VoxPreset
- type: Metabolism
- type: Respirator
needsGases:
Nitrogen: 0.00060763888
producesGases:

View File

@@ -183,9 +183,6 @@
desc: A combination of cream, wine and moonshine. Why would you do this?
physicalDesc: foul
color: "#a6969a"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: Lean
@@ -193,9 +190,6 @@
desc: Turn up for days.
physicalDesc: bubbly
color: "#9400D3"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: LeanShine
@@ -203,8 +197,3 @@
desc: Lean mixed with moonshine. Turn up for months.
physicalDesc: bubbly
color: "#9d5fb8"
metabolism:
- !type:DefaultDrink
rate: 1

View File

@@ -4,9 +4,6 @@
desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine.
physicalDesc: aromatic
color: "#664300"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: Tea
@@ -14,9 +11,6 @@
desc: A drink made by boiling leaves of the tea tree, Camellia sinensis.
physicalDesc: aromatic
color: "#8a5a3a"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: Cream
@@ -24,9 +18,6 @@
desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh?
physicalDesc: creamy
color: "#DFD7AF"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: Milk
@@ -34,9 +25,6 @@
desc: An opaque white liquid produced by the mammary glands of mammals.
physicalDesc: opaque
color: "#DFDFDF"
metabolism:
- !type:DefaultDrink
rate: 1
plantMetabolism:
- !type:AdjustNutrition
amount: 0.1
@@ -49,9 +37,6 @@
desc: This milk has gone rancid.
physicalDesc: putrid
color: "#faffba"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: MilkSoy
@@ -59,9 +44,6 @@
desc: Surprisingly tasty.
physicalDesc: refreshing
color: "#302000"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: MilkOat
@@ -69,6 +51,3 @@
desc: Surprisingly tasty.
physicalDesc: refreshing
color: "#302000"
metabolism:
- !type:DefaultDrink
rate: 1

View File

@@ -4,9 +4,6 @@
desc: It's a little piece of Eden.
physicalDesc: crisp
color: "#FDAD01"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceBerry
@@ -14,9 +11,6 @@
desc: A delicious blend of several different kinds of berries.
physicalDesc: sweet
color: "#660099"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceBanana
@@ -24,9 +18,6 @@
desc: The raw essence of a banana. HONK.
physicalDesc: crisp
color: "#FFE777"
metabolism:
- !type:DefaultDrink
rate: 1
#TODO: port on_mob_life: restore eyesight
#if(..())
@@ -45,9 +36,6 @@
desc: It's like a carrot, but less crunchy.
physicalDesc: crisp
color: "#FF8820"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceLime
@@ -55,9 +43,6 @@
desc: The sweet-sour juice of limes.
physicalDesc: citric
color: "#99bb43"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceLemon
@@ -65,9 +50,6 @@
desc: This juice is VERY sour.
physicalDesc: citric
color: "#fff690"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceGrape
@@ -75,9 +57,6 @@
desc: Freshly squeezed juice from red grapes. Quite sweet.
physicalDesc: crisp
color: "#512284"
metabolism:
- !type:DefaultDrink
rate: 1
# /datum/reagent/drink/orangejuice/on_mob_life(var/mob/living/M)
@@ -93,9 +72,6 @@
desc: Both delicious AND rich in Vitamin C. What more do you need?
physicalDesc: citric
color: "#E78108"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceTomato
@@ -103,9 +79,6 @@
desc: Tomatoes made into juice. What a waste of good tomatoes, huh?
physicalDesc: saucey
color: "#731008"
metabolism:
- !type:DefaultDrink
rate: 1
# /datum/reagent/drink/poisonberryjuice/on_mob_life(var/mob/living/M)
@@ -120,9 +93,6 @@
desc: A surprisingly tasty juice blended from various kinds of very deadly and toxic berries.
physicalDesc: aromatic #maybe should be 'sickly'?
color: "#6600CC"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuiceWatermelon
@@ -130,9 +100,6 @@
desc: The delicious juice of a watermelon.
physicalDesc: sweet
color: "#EF3520"
metabolism:
- !type:DefaultDrink
rate: 1
- type: reagent
id: JuicePineapple
@@ -140,9 +107,6 @@
desc: The delicious juice of a pineapple.
physicalDesc: tropical
color: yellow
metabolism:
- !type:DefaultDrink
rate: 1
# - type: reagent
# id: PotatoJuice
@@ -150,6 +114,3 @@
# desc: Juice of the potato. Bleh.
# physicalDesc: starchy
# color: "#302000"
# metabolism:
# - !type:DefaultDrink
# rate: 1

View File

@@ -4,9 +4,6 @@
desc: A sweet, carbonated soft drink. Caffeine free.
physicalDesc: fizzy
color: "#422912"
metabolism:
- !type:DefaultDrink
rate: 1
plantMetabolism:
- !type:AdjustNutrition
amount: 0.1
@@ -21,12 +18,6 @@
desc: A highly processed liquid substance barely-passing intergalatic health standarts for a soft drink.
physicalDesc: fizzy
color: "#deb928"
metabolism:
- !type:DefaultDrink
rate: 1
- !type:HealthChangeMetabolism
healthChange: 2
damageClass: Toxin
plantMetabolism:
- !type:AdjustNutrition
amount: 0.1

View File

@@ -4,9 +4,6 @@
desc: The sweetness of a thousand sugars but none of the calories.
# physicalDesc:
color: aquamarine
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: BbqSauce
@@ -14,9 +11,6 @@
desc: Hand wipes not included.
physicalDesc: Gloopy.
color: darkred
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Cornoil
@@ -24,9 +18,6 @@
desc: Corn oil, A delicious oil used in cooking. Made from corn.
# physicalDesc:
color: yellow
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Frostoil
@@ -34,9 +25,6 @@
desc: Leaves the tongue numb in its passage.
# physicalDesc:
color: skyblue
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: HorseradishSauce
@@ -44,9 +32,6 @@
desc: Smelly horseradish sauce.
physicalDesc: Overpowering.
color: gray
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Hotsauce
@@ -54,9 +39,6 @@
desc: Burns so good.
# physicalDesc:
color: red
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Ketchup
@@ -64,9 +46,6 @@
desc: Made from pureed tomatoes and flavored with spices.
# physicalDesc:
color: red
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Soysauce
@@ -74,6 +53,3 @@
desc: A salty soy-based flavoring.
# physicalDesc:
color: saddlebrown
metabolism:
- !type:DefaultFood
rate: 1

View File

@@ -4,9 +4,6 @@
desc: Used for baking.
physicalDesc: powdery
color: white
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Oats
@@ -14,18 +11,12 @@
desc: Used for a variety of tasty purposes.
physicalDesc: coarse
color: tan
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Enzyme
name: universal enzyme
desc: Used in cooking various dishes.
color: "#009900"
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Egg
@@ -33,9 +24,6 @@
desc: Used for baking.
physicalDesc: mucus-like
color: white
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Sugar
@@ -43,9 +31,6 @@
desc: Tasty spacey sugar!
physicalDesc:
color: white
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Salt
@@ -53,9 +38,6 @@
desc: Salt. From space oceans, presumably.
physicalDesc: Coarse.
color: white
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Blackpepper
@@ -63,33 +45,21 @@
desc: Often used to flavor food or make people sneeze.
physicalDesc: Grainy.
color: black
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Vinegar
name: vinegar
desc: Often used to flavor food.
color: tan
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: Rice
name: rice
desc: Hard, small white grains.
color: white
metabolism:
- !type:DefaultFood
rate: 1
- type: reagent
id: OilOlive
name: olive oil
desc: Viscous and fragrant.
color: olive
metabolism:
- !type:DefaultFood
rate: 1

View File

@@ -89,9 +89,6 @@
desc: All the vitamins, minerals, and carbohydrates the body needs in pure form.
physicalDesc: opaque
color: "#664330"
metabolism:
- !type:DefaultFood
rate: 1
plantMetabolism:
- !type:AdjustNutrition
amount: 1
@@ -218,9 +215,6 @@
color: "#c0e0ff20"
boilingPoint: 100.0
meltingPoint: 0.0
metabolism:
- !type:DefaultDrink
rate: 1
tileReactions:
- !type:ExtinguishTileReaction {}
- !type:SpillIfPuddlePresentTileReaction {}

View File

@@ -18,10 +18,6 @@
desc: A broad-spectrum anti-toxin, which treats toxin damage in the blood stream. Overdosing will cause vomiting, dizzyness and pain.
physicalDesc: translucent
color: "#3a1d8a"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -1
damageClass: Toxin
plantMetabolism:
- !type:AdjustToxins
amount: -10
@@ -34,13 +30,6 @@
desc: A slightly unstable medication used for the most extreme any serious case of radiation poisoning. Lowers radiation level at over twice the rate Hyronalin does and will heal toxin damage at the same time. Deals very minor brute damage to the patient over time, but the patient's body will typically out-regenerate it easily.
physicalDesc: cloudy
color: "#bd5902"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -1
damageClass: Toxin #I think you need multiple of these components for different types of damage so I'll maybe change it to work better in the future
- !type:HealthChangeMetabolism
healthChange: 0.5
damageClass: Brute
- type: reagent
id: Bicaridine
@@ -48,10 +37,6 @@
desc: An analgesic which is highly effective at treating brute damage. It is useful for stabilizing people who have been severely beaten, as well as treating less life-threatening injuries. In the case of bleeding (internal or external), bicaridine will slow down the bleeding heavily. If the dosage exceeds the overdose limit, it'll stop it outright.
physicalDesc: opaque
color: "#ffaa00"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -2
damageClass: Brute
- type: reagent
id: Cryoxadone
@@ -90,10 +75,6 @@
desc: An advanced chemical that is more effective at treating burn damage than Kelotane.
physicalDesc: translucent
color: "#215263"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -3
damageClass: Burn
- type: reagent
id: Dexalin
@@ -101,10 +82,6 @@
desc: Used for treating oxygen deprivation. In most cases where it is likely to be needed, the strength of Dexalin Plus will probably be more useful (Results in 1 unit instead of 2).
physicalDesc: opaque
color: "#0041a8"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -1
damageClass: Airloss #this may be asphyxiation
- type: reagent
id: DexalinPlus
@@ -112,10 +89,6 @@
desc: Used in treatment of extreme cases of oxygen deprivation. Even a single unit immediately counters all oxygen loss, which is hugely useful in many circumstances. Any dose beyond this will continue to counter oxygen loss until it is metabolized, essentially removing the need to breathe.
physicalDesc: cloudy
color: "#4da0bd"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -3
damageClass: Airloss
- type: reagent
id: Ethylredoxrazine
@@ -165,10 +138,6 @@
desc: Treats burn damage and prevents infection.
physicalDesc: strong-smelling
color: "#bf3d19"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -1
damageClass: Burn
- type: reagent
id: Leporazine
@@ -225,11 +194,6 @@
desc: Toxic, but treats hallucinations, drowsiness & halves the duration of paralysis, stuns and knockdowns. It is metabolized very slowly. One unit is enough to treat hallucinations; two units is deadly.
physicalDesc: pungent
color: "#d49a2f"
metabolism:
- !type:HealthChangeMetabolism
healthChange: 0.67
damageClass: Toxin
rate: 0.2
- type: reagent
id: Tramadol
@@ -289,10 +253,6 @@
plantMetabolism:
- !type:AdjustToxins
amount: 10
metabolism:
- !type:HealthChangeMetabolism
healthChange: 1
damageClass: Airloss
- type: reagent
id: Impedrezene
@@ -307,10 +267,6 @@
desc: Temporarily stops respiration and causes tissue damage. Large doses are fatal, and will cause people to pass out very quickly. Dexalin and Dexalin Plus will both remove it, however.
physicalDesc: pungent
color: "#6b0007"
metabolism:
- !type:HealthChangeMetabolism
healthChange: 7
damageClass: Airloss
- type: reagent
id: Lipozine
@@ -363,17 +319,3 @@
desc: A soothing milky liquid with an iridescent gleam. A well known conspiracy theory says that it's origins remain a mystery because knowing the secrets of its production would render most commercial pharmaceuticals obsolete.
physicalDesc: soothing
color: "#fcf7f9"
metabolism:
- !type:HealthChangeMetabolism
healthChange: -2
damageClass: Burn
- !type:HealthChangeMetabolism
healthChange: -2
damageClass: Toxin
- !type:HealthChangeMetabolism
healthChange: -2
damageClass: Airloss
- !type:HealthChangeMetabolism
healthChange: -2
damageClass: Brute