diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
new file mode 100644
index 0000000000..736e22f834
--- /dev/null
+++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Chemistry;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+///
+public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
+{
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnReceiveRegistryUpdate);
+ }
+
+ private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
+ {
+ var data = message.Changeset;
+ foreach (var remove in data.Removed)
+ {
+ Registry.Remove(remove);
+ }
+
+ foreach (var (key, val) in data.GuideEntries)
+ {
+ Registry[key] = val;
+ }
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
new file mode 100644
index 0000000000..e8a36d57f4
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
new file mode 100644
index 0000000000..4b832be02b
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
@@ -0,0 +1,176 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Guidebook.Richtext;
+using Content.Client.Message;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Guidebook.Controls;
+
+///
+/// Control for embedding a reagent into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IEntitySystemManager _systemManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ private readonly ChemistryGuideDataSystem _chemistryGuideData;
+
+ public GuideReagentEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _chemistryGuideData = _systemManager.GetEntitySystem();
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideReagentEmbed(string reagent) : this()
+ {
+ GenerateControl(_prototype.Index(reagent));
+ }
+
+ public GuideReagentEmbed(ReagentPrototype reagent) : this()
+ {
+ GenerateControl(reagent);
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Reagent", out var id))
+ {
+ Logger.Error("Reagent embed tag is missing reagent prototype argument");
+ return false;
+ }
+
+ if (!_prototype.TryIndex(id, out var reagent))
+ {
+ Logger.Error($"Specified reagent prototype \"{id}\" is not a valid reagent prototype");
+ return false;
+ }
+
+ GenerateControl(reagent);
+
+ control = this;
+ return true;
+ }
+
+ private void GenerateControl(ReagentPrototype reagent)
+ {
+ NameBackground.PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = reagent.SubstanceColor
+ };
+
+ var textColor = Color.ToHsl(reagent.SubstanceColor).Z > 0.45
+ ? Color.Black
+ : Color.White;
+
+ ReagentName.SetMarkup(Loc.GetString("guidebook-reagent-name",
+ ("color", textColor), ("name", reagent.LocalizedName)));
+
+ #region Recipe
+ // by default, we assume that the reaction has the same ID as the reagent.
+ // if this isn't true, we'll loop through reactions.
+ if (!_prototype.TryIndex(reagent.ID, out var reactionPrototype))
+ {
+ reactionPrototype = _prototype.EnumeratePrototypes()
+ .FirstOrDefault(p => p.Products.ContainsKey(reagent.ID));
+ }
+
+ if (reactionPrototype != null)
+ {
+ var reactantMsg = new FormattedMessage();
+ var reactantsCount = reactionPrototype.Reactants.Count;
+ var i = 0;
+ foreach (var (product, reactant) in reactionPrototype.Reactants)
+ {
+ reactantMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ ("reagent", _prototype.Index(product).LocalizedName), ("ratio", reactant.Amount)));
+ i++;
+ if (i < reactantsCount)
+ reactantMsg.PushNewline();
+ }
+ reactantMsg.Pop();
+ ReactantsLabel.SetMessage(reactantMsg);
+
+ var productMsg = new FormattedMessage();
+ var productCount = reactionPrototype.Products.Count;
+ var u = 0;
+ foreach (var (product, ratio) in reactionPrototype.Products)
+ {
+ productMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ ("reagent", _prototype.Index(product).LocalizedName), ("ratio", ratio)));
+ u++;
+ if (u < productCount)
+ productMsg.PushNewline();
+ }
+ productMsg.Pop();
+ ProductsLabel.SetMessage(productMsg);
+ }
+ else
+ {
+ RecipesContainer.Visible = false;
+ }
+ #endregion
+
+ #region Effects
+ if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistry) &&
+ guideEntryRegistry.GuideEntries != null &&
+ guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any()))
+ {
+ EffectsDescriptionContainer.Children.Clear();
+ foreach (var (group, effect) in guideEntryRegistry.GuideEntries)
+ {
+ if (!effect.EffectDescriptions.Any())
+ continue;
+
+ var groupLabel = new RichTextLabel();
+ groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
+ ("group", group), ("rate", effect.MetabolismRate)));
+ var descriptionLabel = new RichTextLabel
+ {
+ Margin = new Thickness(25, 0, 10, 0)
+ };
+
+ var descMsg = new FormattedMessage();
+ var descriptionsCount = effect.EffectDescriptions.Length;
+ var i = 0;
+ foreach (var effectString in effect.EffectDescriptions)
+ {
+ descMsg.AddMarkup(effectString);
+ i++;
+ if (i < descriptionsCount)
+ descMsg.PushNewline();
+ }
+ descriptionLabel.SetMessage(descMsg);
+
+ EffectsDescriptionContainer.AddChild(groupLabel);
+ EffectsDescriptionContainer.AddChild(descriptionLabel);
+ }
+ }
+ else
+ {
+ EffectsContainer.Visible = false;
+ }
+ #endregion
+
+ FormattedMessage description = new();
+ description.AddText(reagent.LocalizedDescription);
+ description.PushNewline();
+ description.AddText(Loc.GetString("guidebook-reagent-physical-description",
+ ("description", reagent.LocalizedPhysicalDescription)));
+ ReagentDescription.SetMessage(description);
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml
new file mode 100644
index 0000000000..da671adaa7
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs
new file mode 100644
index 0000000000..0c9356eccb
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs
@@ -0,0 +1,60 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Guidebook.Richtext;
+using Content.Shared.Chemistry.Reagent;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Guidebook.Controls;
+
+///
+/// Control for embedding a reagent into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideReagentGroupEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public GuideReagentGroupEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideReagentGroupEmbed(string group) : this()
+ {
+ var prototypes = _prototype.EnumeratePrototypes()
+ .Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
+ foreach (var reagent in prototypes)
+ {
+ var embed = new GuideReagentEmbed(reagent);
+ GroupContainer.AddChild(embed);
+ }
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Group", out var group))
+ {
+ Logger.Error("Reagent group embed tag is missing group argument");
+ return false;
+ }
+
+ var prototypes = _prototype.EnumeratePrototypes()
+ .Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
+ foreach (var reagent in prototypes)
+ {
+ var embed = new GuideReagentEmbed(reagent);
+ GroupContainer.AddChild(embed);
+ }
+
+ control = this;
+ return true;
+ }
+}
diff --git a/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
new file mode 100644
index 0000000000..70a79254d6
--- /dev/null
+++ b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
@@ -0,0 +1,47 @@
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Console;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Chemistry.Commands;
+
+[AdminCommand(AdminFlags.Debug)]
+public sealed class DumpReagentGuideText : IConsoleCommand
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IEntitySystemManager _entSys = default!;
+
+ public string Command => "dumpreagentguidetext";
+ public string Description => "Dumps the guidebook text for a reagent to the console";
+ public string Help => "dumpreagentguidetext ";
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 1)
+ {
+ shell.WriteError("Must have only 1 argument");
+ return;
+ }
+
+ if (!_prototype.TryIndex(args[0], out var reagent))
+ {
+ shell.WriteError($"Invalid prototype: {args[0]}");
+ return;
+ }
+
+ if (reagent.Metabolisms is null)
+ {
+ shell.WriteLine("Nothing to dump.");
+ return;
+ }
+
+ foreach (var (_, entry) in reagent.Metabolisms)
+ {
+ foreach (var effect in entry.Effects)
+ {
+ shell.WriteLine(effect.GuidebookEffectDescription(_prototype, _entSys) ?? $"[skipped effect of type {effect.GetType()}]");
+ }
+ }
+ }
+}
diff --git a/Content.Server/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
new file mode 100644
index 0000000000..8cbdd82f07
--- /dev/null
+++ b/Content.Server/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
@@ -0,0 +1,67 @@
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Reagent;
+using Robust.Server.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Chemistry.EntitySystems;
+
+
+public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ PrototypeManager.PrototypesReloaded += PrototypeManagerReload;
+
+ _player.PlayerStatusChanged += OnPlayerStatusChanged;
+
+ InitializeServerRegistry();
+ }
+
+ private void InitializeServerRegistry()
+ {
+ var changeset = new ReagentGuideChangeset(new Dictionary(), new HashSet());
+ foreach (var proto in PrototypeManager.EnumeratePrototypes())
+ {
+ var entry = new ReagentGuideEntry(proto, PrototypeManager, EntityManager.EntitySysManager);
+ changeset.GuideEntries.Add(proto.ID, entry);
+ Registry[proto.ID] = entry;
+ }
+
+ var ev = new ReagentGuideRegistryChangedEvent(changeset);
+ RaiseNetworkEvent(ev);
+ }
+
+ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
+ {
+ if (e.NewStatus != SessionStatus.Connected)
+ return;
+
+ var sendEv = new ReagentGuideRegistryChangedEvent(new ReagentGuideChangeset(Registry, new HashSet()));
+ RaiseNetworkEvent(sendEv, e.Session);
+ }
+
+ private void PrototypeManagerReload(PrototypesReloadedEventArgs obj)
+ {
+ if (!obj.ByType.TryGetValue(typeof(ReagentPrototype), out var reagents))
+ return;
+
+ var changeset = new ReagentGuideChangeset(new Dictionary(), new HashSet());
+
+ foreach (var (id, proto) in reagents.Modified)
+ {
+ var reagentProto = (ReagentPrototype) proto;
+ var entry = new ReagentGuideEntry(reagentProto, PrototypeManager, EntityManager.EntitySysManager);
+ changeset.GuideEntries.Add(id, entry);
+ Registry[id] = entry;
+ }
+
+ var ev = new ReagentGuideRegistryChangedEvent(changeset);
+ RaiseNetworkEvent(ev);
+ }
+}
diff --git a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs
index 8db6ef2f93..8c1c3f4835 100644
--- a/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs
+++ b/Content.Server/Chemistry/ReactionEffects/AreaReactionEffect.cs
@@ -45,6 +45,10 @@ namespace Content.Server.Chemistry.ReactionEffects
[DataField("sound", required: true)] private SoundSpecifier _sound = default!;
public override bool ShouldLog => true;
+
+ protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-missing");
+
public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs
index 52a00399c4..e87b4ca771 100644
--- a/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs
+++ b/Content.Server/Chemistry/ReactionEffects/CreateEntityReactionEffect.cs
@@ -19,6 +19,12 @@ public sealed class CreateEntityReactionEffect : ReagentEffect
[DataField("number")]
public uint Number = 1;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect",
+ ("chance", Probability),
+ ("entname", IoCManager.Resolve().Index(Entity).Name),
+ ("amount", Number));
+
public override void Effect(ReagentEffectArgs args)
{
var transform = args.EntityManager.GetComponent(args.SolutionEntity);
diff --git a/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
index 0230949bad..7b5c0bfce7 100644
--- a/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
+++ b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs
@@ -3,6 +3,7 @@ using Content.Server.Explosion.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.Explosion;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReactionEffects
@@ -24,7 +25,7 @@ namespace Content.Server.Chemistry.ReactionEffects
[DataField("maxIntensity")]
[JsonIgnore]
public float MaxIntensity = 5;
-
+
///
/// How quickly intensity drops off as you move away from the epicenter
///
@@ -51,6 +52,9 @@ namespace Content.Server.Chemistry.ReactionEffects
public float IntensityPerUnit = 1;
public override bool ShouldLog => true;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs
new file mode 100644
index 0000000000..5f282702bb
--- /dev/null
+++ b/Content.Server/Chemistry/ReactionEffects/SmokeAreaReactionEffect.cs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs b/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs
index 4d30ec4643..d768935466 100644
--- a/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs
+++ b/Content.Server/Chemistry/ReactionEffects/SolutionTemperatureEffects.cs
@@ -16,6 +16,10 @@ namespace Content.Server.Chemistry.ReactionEffects
///
[DataField("temperature", required: true)] private float _temperature;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-set-solution-temperature-effect",
+ ("chance", Probability), ("temperature", _temperature));
+
public override void Effect(ReagentEffectArgs args)
{
var solution = args.Source;
@@ -52,6 +56,10 @@ namespace Content.Server.Chemistry.ReactionEffects
///
[DataField("scaled")] private bool _scaled;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
+ ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
+
public override void Effect(ReagentEffectArgs args)
{
var solution = args.Source;
@@ -101,11 +109,15 @@ namespace Content.Server.Chemistry.ReactionEffects
var heatCap = solution.GetHeatCapacity(null);
var deltaT = _scaled
- ? _delta / heatCap * (float) args.Quantity
+ ? _delta / heatCap * (float) args.Quantity
: _delta / heatCap;
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
+ ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/BodyTemperature.cs b/Content.Server/Chemistry/ReagentEffectConditions/BodyTemperature.cs
index 313d1eb629..057d2ebdb8 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/BodyTemperature.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/BodyTemperature.cs
@@ -1,5 +1,6 @@
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
@@ -13,7 +14,7 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
public float Min = 0;
[DataField("max")]
- public float Max = float.MaxValue;
+ public float Max = float.PositiveInfinity;
public override bool Condition(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))
@@ -24,5 +25,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
+ ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
+ ("min", Min));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/HasTagCondition.cs b/Content.Server/Chemistry/ReagentEffectConditions/HasTagCondition.cs
index 2c57bda77e..dccf4f33eb 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/HasTagCondition.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/HasTagCondition.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Tag;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions;
@@ -21,4 +22,10 @@ public sealed class HasTag : ReagentEffectCondition
return false;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ // this should somehow be made (much) nicer.
+ return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Invert));
+ }
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/MobStateCondition.cs b/Content.Server/Chemistry/ReagentEffectConditions/MobStateCondition.cs
index 45d8ddda07..b76e8fac3c 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/MobStateCondition.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/MobStateCondition.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
@@ -21,6 +22,11 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", mobstate));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs
index d494b07089..cbb0ec2f52 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs
@@ -1,6 +1,7 @@
using Content.Server.Body.Components;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions
@@ -30,5 +31,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return ShouldHave;
return !ShouldHave;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
+ ("name", prototype.Index(Type).Name),
+ ("shouldhave", ShouldHave));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs
index c97b29a94c..befefd1f53 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs
@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
@@ -35,5 +36,17 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return quant >= Min && quant <= Max;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ ReagentPrototype? reagentProto = null;
+ if (Reagent is not null)
+ prototype.TryIndex(Reagent, out reagentProto);
+
+ return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
+ ("reagent", reagentProto?.LocalizedName ?? "this reagent"),
+ ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
+ ("min", Min.Float()));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/SolutionTemperature.cs b/Content.Server/Chemistry/ReagentEffectConditions/SolutionTemperature.cs
index da646fb1c7..6b370fb9a4 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/SolutionTemperature.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/SolutionTemperature.cs
@@ -1,4 +1,5 @@
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
@@ -24,5 +25,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return true;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-solution-temperature",
+ ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
+ ("min", Min));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffectConditions/TotalDamage.cs b/Content.Server/Chemistry/ReagentEffectConditions/TotalDamage.cs
index 4758bccea5..8c62dd7ce0 100644
--- a/Content.Server/Chemistry/ReagentEffectConditions/TotalDamage.cs
+++ b/Content.Server/Chemistry/ReagentEffectConditions/TotalDamage.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
@@ -23,5 +24,12 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
return false;
}
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-total-damage",
+ ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
+ ("min", Min.Float()));
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
index 04ae4ca6f5..bd89e8ec45 100644
--- a/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs
@@ -1,5 +1,6 @@
using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -10,4 +11,7 @@ public sealed class ActivateArtifact : ReagentEffect
var artifact = args.EntityManager.EntitySysManager.GetEntitySystem();
artifact.TryActivateArtifact(args.SolutionEntity);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("reagent-effect-guidebook-activate-artifact", ("chance", Probability));
}
diff --git a/Content.Server/Chemistry/ReagentEffects/AddToSolutionReaction.cs b/Content.Server/Chemistry/ReagentEffects/AddToSolutionReaction.cs
index f687f19e7d..5ec0b2a08b 100644
--- a/Content.Server/Chemistry/ReagentEffects/AddToSolutionReaction.cs
+++ b/Content.Server/Chemistry/ReagentEffects/AddToSolutionReaction.cs
@@ -1,6 +1,7 @@
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -24,5 +25,8 @@ namespace Content.Server.Chemistry.ReagentEffects
.TryAddReagent(args.SolutionEntity, solutionContainer, args.Reagent.ID, args.Quantity, out var accepted))
args.Source?.RemoveReagent(args.Reagent.ID, accepted);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
index 44f33a6bb5..acae2e0153 100644
--- a/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
+++ b/Content.Server/Chemistry/ReagentEffects/AdjustAlert.cs
@@ -1,5 +1,6 @@
using Content.Shared.Alert;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -18,6 +19,9 @@ public sealed class AdjustAlert : ReagentEffect
[DataField("time")]
public float Time;
+ //JUSTIFICATION: This just changes some visuals, doesn't need to be documented.
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+
public override void Effect(ReagentEffectArgs args)
{
var alertSys = EntitySystem.Get();
diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs b/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs
index 7e716928ec..c29459b30f 100644
--- a/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs
+++ b/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs
@@ -60,5 +60,27 @@ namespace Content.Server.Chemistry.ReagentEffects
}
}
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ if (Reagent is not null && prototype.TryIndex(Reagent, out ReagentPrototype? reagentProto))
+ {
+ return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent",
+ ("chance", Probability),
+ ("deltasign", MathF.Sign(Amount.Float())),
+ ("reagent", reagentProto.LocalizedName),
+ ("amount", MathF.Abs(Amount.Float())));
+ }
+ else if (Group is not null && prototype.TryIndex(Group, out MetabolismGroupPrototype? groupProto))
+ {
+ return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group",
+ ("chance", Probability),
+ ("deltasign", MathF.Sign(Amount.Float())),
+ ("group", groupProto.ID),
+ ("amount", MathF.Abs(Amount.Float())));
+ }
+
+ throw new NotImplementedException();
+ }
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustTemperature.cs b/Content.Server/Chemistry/ReagentEffects/AdjustTemperature.cs
index 45c07fa2a3..b96d2a0469 100644
--- a/Content.Server/Chemistry/ReagentEffects/AdjustTemperature.cs
+++ b/Content.Server/Chemistry/ReagentEffects/AdjustTemperature.cs
@@ -1,6 +1,7 @@
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -9,6 +10,12 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("amount")]
public float Amount;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-adjust-temperature",
+ ("chance", Probability),
+ ("deltasign", MathF.Sign(Amount)),
+ ("amount", MathF.Abs(Amount)));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))
diff --git a/Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs b/Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs
index 376398d7b7..132315663f 100644
--- a/Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Content.Server.Body.Systems;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReactionEffects
{
@@ -12,6 +13,10 @@ namespace Content.Server.Chemistry.ReactionEffects
{
[DataField("cleanseRate")]
public float CleanseRate = 3.0f;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.Source == null || args.Reagent == null)
diff --git a/Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs b/Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs
index 0826e99110..f95c52ba40 100644
--- a/Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs
@@ -2,6 +2,7 @@ using Content.Shared.Chemistry.Reagent;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -17,6 +18,9 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("amount")]
public int Amount = -1;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-cure-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount)));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.Scale != 1f) // huh?
diff --git a/Content.Server/Chemistry/ReagentEffects/ChemVomit.cs b/Content.Server/Chemistry/ReagentEffects/ChemVomit.cs
index 8e923e06bf..74ecfefff4 100644
--- a/Content.Server/Chemistry/ReagentEffects/ChemVomit.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ChemVomit.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Server.Medical;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -17,6 +18,9 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("hungerAmount")]
public float HungerAmount = -40f;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.Scale != 1f)
diff --git a/Content.Server/Chemistry/ReagentEffects/CreateGas.cs b/Content.Server/Chemistry/ReagentEffects/CreateGas.cs
index a2a0c61124..0778c0727f 100644
--- a/Content.Server/Chemistry/ReagentEffects/CreateGas.cs
+++ b/Content.Server/Chemistry/ReagentEffects/CreateGas.cs
@@ -2,6 +2,7 @@
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -17,6 +18,17 @@ public sealed class CreateGas : ReagentEffect
public float Multiplier = 3f;
public override bool ShouldLog => true;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var atmos = entSys.GetEntitySystem();
+ var gasProto = atmos.GetGas(Gas);
+
+ return Loc.GetString("reagent-effect-guidebook-create-gas",
+ ("chance", Probability),
+ ("moles", Multiplier),
+ ("gas", gasProto.Name));
+ }
+
public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReagentEffects/Drunk.cs b/Content.Server/Chemistry/ReagentEffects/Drunk.cs
index c5d24e362d..dedf984b2b 100644
--- a/Content.Server/Chemistry/ReagentEffects/Drunk.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Drunk.cs
@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Drunk;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -17,6 +18,9 @@ public sealed class Drunk : ReagentEffect
[DataField("slurSpeech")]
public bool SlurSpeech = true;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
var boozePower = BoozePower;
diff --git a/Content.Server/Chemistry/ReagentEffects/Electrocute.cs b/Content.Server/Chemistry/ReagentEffects/Electrocute.cs
index 08b2df966e..b6d2d58e28 100644
--- a/Content.Server/Chemistry/ReagentEffects/Electrocute.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Electrocute.cs
@@ -1,5 +1,6 @@
using Content.Server.Electrocution;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -14,6 +15,9 @@ public sealed class Electrocute : ReagentEffect
///
[DataField("refresh")] public bool Refresh = true;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime));
+
public override bool ShouldLog => true;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReagentEffects/Emote.cs b/Content.Server/Chemistry/ReagentEffects/Emote.cs
index fa3cf937d0..753435a599 100644
--- a/Content.Server/Chemistry/ReagentEffects/Emote.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Emote.cs
@@ -2,6 +2,7 @@ using Content.Server.Chat.Systems;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -18,6 +19,10 @@ public sealed class Emote : ReagentEffect
[DataField("showInChat")]
public bool ShowInChat;
+ // JUSTIFICATION: Emoting is flavor, so same reason popup messages are not in here.
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => null;
+
public override void Effect(ReagentEffectArgs args)
{
if (EmoteId == null)
diff --git a/Content.Server/Chemistry/ReagentEffects/ExtinguishReaction.cs b/Content.Server/Chemistry/ReagentEffects/ExtinguishReaction.cs
index 6b7c56518a..eb2aefd444 100644
--- a/Content.Server/Chemistry/ReagentEffects/ExtinguishReaction.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ExtinguishReaction.cs
@@ -2,12 +2,16 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed class ExtinguishReaction : ReagentEffect
{
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-extinguish-reaction", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return;
diff --git a/Content.Server/Chemistry/ReagentEffects/FlammableReaction.cs b/Content.Server/Chemistry/ReagentEffects/FlammableReaction.cs
index 54d3689135..0cf28d1ac1 100644
--- a/Content.Server/Chemistry/ReagentEffects/FlammableReaction.cs
+++ b/Content.Server/Chemistry/ReagentEffects/FlammableReaction.cs
@@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -13,6 +14,10 @@ namespace Content.Server.Chemistry.ReagentEffects
public float Multiplier = 0.05f;
public override bool ShouldLog => true;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability));
+
public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReagentEffects/HealthChange.cs b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs
index ec5ffa32ab..59f11695fa 100644
--- a/Content.Server/Chemistry/ReagentEffects/HealthChange.cs
+++ b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs
@@ -1,8 +1,11 @@
+using System.Linq;
using System.Text.Json.Serialization;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -31,6 +34,38 @@ namespace Content.Server.Chemistry.ReagentEffects
[JsonPropertyName("ignoreResistances")]
public bool IgnoreResistances = true;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var damages = new List();
+ var heals = false;
+ var deals = false;
+
+ // TODO: This should be smarter. Namely, not showing a damage type as being in a group unless every damage type in the group is present and equal in value.
+ foreach (var (kind, amount) in Damage.GetDamagePerGroup())
+ {
+ var sign = MathF.Sign(amount.Float());
+
+ if (sign < 0)
+ heals = true;
+ if (sign > 0)
+ deals = true;
+
+ damages.Add(
+ Loc.GetString("health-change-display",
+ ("kind", kind),
+ ("amount", MathF.Abs(amount.Float())),
+ ("deltasign", sign)
+ ));
+ }
+
+ var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
+
+ return Loc.GetString("reagent-effect-guidebook-health-change",
+ ("chance", Probability),
+ ("changes", ContentLocalizationManager.FormatList(damages)),
+ ("healsordeals", healsordeals));
+ }
+
public override void Effect(ReagentEffectArgs args)
{
var scale = ScaleByQuantity ? args.Quantity : FixedPoint2.New(1);
diff --git a/Content.Server/Chemistry/ReagentEffects/Ignite.cs b/Content.Server/Chemistry/ReagentEffects/Ignite.cs
index 5eb19297b4..c24183bbde 100644
--- a/Content.Server/Chemistry/ReagentEffects/Ignite.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Ignite.cs
@@ -1,6 +1,7 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -10,6 +11,10 @@ namespace Content.Server.Chemistry.ReagentEffects;
public sealed class Ignite : ReagentEffect
{
public override bool ShouldLog => true;
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability));
+
public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(ReagentEffectArgs args)
diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs
index 93684df7d7..5919ae3edb 100644
--- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs
+++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs
@@ -2,11 +2,16 @@ using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Components;
using Content.Server.Speech.Components;
using Content.Shared.Chemistry.Reagent;
+using Content.Server.Ghost.Roles.Components;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
public sealed class MakeSentient : ReagentEffect
{
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
var entityManager = args.EntityManager;
diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyBleedAmount.cs b/Content.Server/Chemistry/ReagentEffects/ModifyBleedAmount.cs
index a80a4d8868..f50bcc4a27 100644
--- a/Content.Server/Chemistry/ReagentEffects/ModifyBleedAmount.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ModifyBleedAmount.cs
@@ -1,6 +1,7 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -12,6 +13,10 @@ public sealed class ModifyBleedAmount : ReagentEffect
[DataField("amount")]
public float Amount = -1.0f;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
+ ("deltasign", MathF.Sign(Amount)));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out var blood))
diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyBloodLevel.cs b/Content.Server/Chemistry/ReagentEffects/ModifyBloodLevel.cs
index f5125fcce3..6a125436a2 100644
--- a/Content.Server/Chemistry/ReagentEffects/ModifyBloodLevel.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ModifyBloodLevel.cs
@@ -2,6 +2,7 @@
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -13,6 +14,10 @@ public sealed class ModifyBloodLevel : ReagentEffect
[DataField("amount")]
public FixedPoint2 Amount = 1.0f;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
+ ("deltasign", MathF.Sign(Amount.Float())));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out var blood))
diff --git a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs
index 63ab04bd66..7cecac7e6b 100644
--- a/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ModifyLungGas.cs
@@ -1,6 +1,7 @@
using Content.Server.Body.Components;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -9,6 +10,10 @@ public sealed class ModifyLungGas : ReagentEffect
[DataField("ratios", required: true)]
private Dictionary _ratios = default!;
+ // JUSTIFICATION: This is internal magic that players never directly interact with.
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => null;
+
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.OrganEntity, out var lung))
diff --git a/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs b/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs
index 9026c03a60..6564346b87 100644
--- a/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs
+++ b/Content.Server/Chemistry/ReagentEffects/MovespeedModifier.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Movement.Systems;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects
@@ -29,6 +30,14 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("statusLifetime")]
public float StatusLifetime = 2f;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return Loc.GetString("reagent-effect-guidebook-movespeed-modifier",
+ ("chance", Probability),
+ ("walkspeed", WalkSpeedModifier),
+ ("time", StatusLifetime));
+ }
+
///
/// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there.
///
diff --git a/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs b/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs
index 4fa6c834e8..f642c1c8a6 100644
--- a/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Oxygenate.cs
@@ -1,6 +1,7 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -9,6 +10,10 @@ public sealed class Oxygenate : ReagentEffect
[DataField("factor")]
public float Factor = 1f;
+ // JUSTIFICATION: This is internal magic that players never directly interact with.
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => null;
+
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out var resp))
diff --git a/Content.Server/Chemistry/ReagentEffects/Paralyze.cs b/Content.Server/Chemistry/ReagentEffects/Paralyze.cs
index 60ffe022f1..8a27de9b07 100644
--- a/Content.Server/Chemistry/ReagentEffects/Paralyze.cs
+++ b/Content.Server/Chemistry/ReagentEffects/Paralyze.cs
@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Server.Stunnable;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -12,6 +13,11 @@ public sealed class Paralyze : ReagentEffect
///
[DataField("refresh")] public bool Refresh = true;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-paralyze",
+ ("chance", Probability),
+ ("time", ParalyzeTime));
+
public override void Effect(ReagentEffectArgs args)
{
var paralyzeTime = ParalyzeTime;
diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs
index b6b59ff213..e8146d0bcc 100644
--- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantAdjustAttribute.cs
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -35,5 +36,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
// Dependencies are never injected for reagents if you intend to do that for this.
return !(Prob <= 0f) && IoCManager.Resolve().Prob(Prob);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs
index 3585f99622..127690ed75 100644
--- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantCryoxadone.cs
@@ -1,6 +1,7 @@
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -26,5 +27,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.SkipAging++;
plantHolderComp.ForceUpdate = true;
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs
index b913bd2270..dbba70dfff 100644
--- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/PlantDiethylamine.cs
@@ -2,6 +2,7 @@ using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -34,5 +35,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.Seed.Endurance++;
}
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
index a13edcad5a..81dd072a62 100644
--- a/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PlantMetabolism/RobustHarvest.cs
@@ -2,6 +2,7 @@ using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
@@ -47,5 +48,7 @@ namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
plantHolderComp.Seed.Yield--;
}
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/PopupMessage.cs b/Content.Server/Chemistry/ReagentEffects/PopupMessage.cs
index 15e12046e2..9d06032307 100644
--- a/Content.Server/Chemistry/ReagentEffects/PopupMessage.cs
+++ b/Content.Server/Chemistry/ReagentEffects/PopupMessage.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Popups;
using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects
@@ -16,6 +17,10 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("visualType")]
public PopupType VisualType = PopupType.Small;
+ // JUSTIFICATION: This is purely cosmetic.
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => null;
+
public override void Effect(ReagentEffectArgs args)
{
var popupSys = args.EntityManager.EntitySysManager.GetEntitySystem();
diff --git a/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs b/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs
index eeaffb8149..20044d4037 100644
--- a/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs
@@ -1,6 +1,7 @@
using Content.Server.Traits.Assorted;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
@@ -16,6 +17,9 @@ public sealed class ResetNarcolepsy : ReagentEffect
[DataField("TimerReset")]
public int TimerReset = 600;
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
if (args.Scale != 1f)
diff --git a/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs b/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs
index 7ef11578ad..a0d00c538e 100644
--- a/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs
+++ b/Content.Server/Chemistry/ReagentEffects/SatiateHunger.cs
@@ -2,6 +2,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -11,10 +12,12 @@ namespace Content.Server.Chemistry.ReagentEffects
///
public sealed class SatiateHunger : ReagentEffect
{
+ private const float DefaultNutritionFactor = 3.0f;
+
///
/// How much hunger is satiated when 1u of the reagent is metabolized
///
- [DataField("factor")] public float NutritionFactor { get; set; } = 3.0f;
+ [DataField("factor")] public float NutritionFactor { get; set; } = DefaultNutritionFactor;
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
public override void Effect(ReagentEffectArgs args)
@@ -24,5 +27,8 @@ namespace Content.Server.Chemistry.ReagentEffects
return;
entman.System().ModifyHunger(args.SolutionEntity, NutritionFactor * (float) args.Quantity, hunger);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", NutritionFactor / DefaultNutritionFactor));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs
index 43e9f051a0..79911110a9 100644
--- a/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs
+++ b/Content.Server/Chemistry/ReagentEffects/SatiateThirst.cs
@@ -1,6 +1,7 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Server.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
@@ -10,10 +11,12 @@ namespace Content.Server.Chemistry.ReagentEffects
///
public sealed class SatiateThirst : ReagentEffect
{
+ private const float DefaultHydrationFactor = 3.0f;
+
/// How much thirst is satiated each metabolism tick. Not currently tied to
/// rate or anything.
[DataField("factor")]
- public float HydrationFactor { get; set; } = 3.0f;
+ public float HydrationFactor { get; set; } = DefaultHydrationFactor;
/// Satiate thirst if a ThirstComponent can be found
public override void Effect(ReagentEffectArgs args)
@@ -21,5 +24,8 @@ namespace Content.Server.Chemistry.ReagentEffects
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out ThirstComponent? thirst))
EntitySystem.Get().UpdateThirst(thirst, HydrationFactor);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", HydrationFactor / DefaultHydrationFactor));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/StatusEffects/GenericStatusEffect.cs b/Content.Server/Chemistry/ReagentEffects/StatusEffects/GenericStatusEffect.cs
index 8727ae2511..982606194a 100644
--- a/Content.Server/Chemistry/ReagentEffects/StatusEffects/GenericStatusEffect.cs
+++ b/Content.Server/Chemistry/ReagentEffects/StatusEffects/GenericStatusEffect.cs
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.StatusEffect;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
{
@@ -57,6 +58,13 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
statusSys.TrySetTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
}
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString(
+ "reagent-effect-guidebook-status-effect",
+ ("chance", Probability),
+ ("type", Type),
+ ("time", Time),
+ ("key", $"reagent-effect-status-effect-{Key}"));
}
public enum StatusEffectMetabolismType
diff --git a/Content.Server/Chemistry/ReagentEffects/StatusEffects/Jitter.cs b/Content.Server/Chemistry/ReagentEffects/StatusEffects/Jitter.cs
index bd060c439a..3aa81e20f6 100644
--- a/Content.Server/Chemistry/ReagentEffects/StatusEffects/Jitter.cs
+++ b/Content.Server/Chemistry/ReagentEffects/StatusEffects/Jitter.cs
@@ -1,5 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Jittering;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
{
@@ -33,5 +34,8 @@ namespace Content.Server.Chemistry.ReagentEffects.StatusEffects
args.EntityManager.EntitySysManager.GetEntitySystem()
.DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency);
}
+
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("reagent-effect-guidebook-jittering", ("chance", Probability));
}
}
diff --git a/Content.Server/Chemistry/ReagentEffects/WashCreamPieReaction.cs b/Content.Server/Chemistry/ReagentEffects/WashCreamPieReaction.cs
index fdd21c8335..6e2e871d2d 100644
--- a/Content.Server/Chemistry/ReagentEffects/WashCreamPieReaction.cs
+++ b/Content.Server/Chemistry/ReagentEffects/WashCreamPieReaction.cs
@@ -2,12 +2,16 @@ using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed class WashCreamPieReaction : ReagentEffect
{
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("reagent-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
+
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out CreamPiedComponent? creamPied)) return;
diff --git a/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs b/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs
index bee2301b28..e5eee21185 100644
--- a/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs
+++ b/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs
@@ -7,5 +7,8 @@ namespace Content.Shared.Body.Prototypes
{
[IdDataField]
public string ID { get; } = default!;
+
+ [DataField("name", required: true)]
+ public string Name { get; } = default!;
}
}
diff --git a/Content.Shared/Chemistry/Reagent/ReagentEffect.cs b/Content.Shared/Chemistry/Reagent/ReagentEffect.cs
index 9140c44f15..1ece3afc13 100644
--- a/Content.Shared/Chemistry/Reagent/ReagentEffect.cs
+++ b/Content.Shared/Chemistry/Reagent/ReagentEffect.cs
@@ -1,8 +1,11 @@
-using System.Text.Json.Serialization;
+using System.Linq;
+using System.Text.Json.Serialization;
using Content.Shared.Chemistry.Components;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Chemistry.Reagent
@@ -23,6 +26,10 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("conditions")]
public ReagentEffectCondition[]? Conditions;
+ public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
+
+ protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys); // => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
+
///
/// What's the chance, from 0 to 1, that this effect will occur?
///
@@ -42,6 +49,23 @@ namespace Content.Shared.Chemistry.Reagent
public virtual bool ShouldLog { get; } = false;
public abstract void Effect(ReagentEffectArgs args);
+
+ ///
+ /// Produces a localized, bbcode'd guidebook description for this effect.
+ ///
+ ///
+ public string? GuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var effect = ReagentEffectGuidebookText(prototype, entSys);
+ if (effect is null)
+ return null;
+
+ return Loc.GetString(ReagentEffectFormat, ("effect", effect), ("chance", Probability),
+ ("conditionCount", Conditions?.Length ?? 0),
+ ("conditions",
+ ContentLocalizationManager.FormatList(Conditions?.Select(x => x.GuidebookExplanation(prototype)).ToList() ??
+ new List())));
+ }
}
public static class ReagentEffectExt
diff --git a/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs b/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs
index 13d1cb37ee..708fe24c70 100644
--- a/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs
+++ b/Content.Shared/Chemistry/Reagent/ReagentEffectCondition.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Chemistry.Reagent
{
@@ -10,5 +11,12 @@ namespace Content.Shared.Chemistry.Reagent
[JsonPropertyName("id")] private protected string _id => this.GetType().Name;
public abstract bool Condition(ReagentEffectArgs args);
+
+ ///
+ /// Effect explanations are of the form "[chance to] [action] when [condition] and [condition]"
+ ///
+ ///
+ ///
+ public abstract string GuidebookExplanation(IPrototypeManager prototype);
}
}
diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
index 001bfd6e00..51bd6c4fe6 100644
--- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
+++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using System.Linq;
+using System.Text.Json.Serialization;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Components;
@@ -9,6 +10,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
@@ -157,6 +159,23 @@ namespace Content.Shared.Chemistry.Reagent
}
}
+ [Serializable, NetSerializable]
+ public struct ReagentGuideEntry
+ {
+ public string ReagentPrototype;
+
+ public Dictionary? GuideEntries;
+
+ public ReagentGuideEntry(ReagentPrototype proto, IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ ReagentPrototype = proto.ID;
+ GuideEntries = proto.Metabolisms?
+ .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
+ .ToDictionary(x => x.Key, x => x.Item2);
+ }
+ }
+
+
[DataDefinition]
public sealed class ReagentEffectsEntry
{
@@ -173,6 +192,30 @@ namespace Content.Shared.Chemistry.Reagent
[JsonPropertyName("effects")]
[DataField("effects", required: true)]
public ReagentEffect[] Effects = default!;
+
+ public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ return new ReagentEffectsGuideEntry(MetabolismRate,
+ Effects
+ .Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
+ .Where(x => x is not null)
+ .Select(x => x!)
+ .ToArray());
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public struct ReagentEffectsGuideEntry
+ {
+ public FixedPoint2 MetabolismRate;
+
+ public string[] EffectDescriptions;
+
+ public ReagentEffectsGuideEntry(FixedPoint2 metabolismRate, string[] effectDescriptions)
+ {
+ MetabolismRate = metabolismRate;
+ EffectDescriptions = effectDescriptions;
+ }
}
[DataDefinition]
diff --git a/Content.Shared/Chemistry/SharedChemistryGuideDataSystem.cs b/Content.Shared/Chemistry/SharedChemistryGuideDataSystem.cs
new file mode 100644
index 0000000000..391134bcf0
--- /dev/null
+++ b/Content.Shared/Chemistry/SharedChemistryGuideDataSystem.cs
@@ -0,0 +1,42 @@
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chemistry;
+
+///
+/// This handles the chemistry guidebook and caching it.
+///
+public abstract class SharedChemistryGuideDataSystem : EntitySystem
+{
+ [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+
+ protected readonly Dictionary Registry = new();
+
+ public IReadOnlyDictionary ReagentGuideRegistry => Registry;
+}
+
+[Serializable, NetSerializable]
+public sealed class ReagentGuideRegistryChangedEvent : EntityEventArgs
+{
+ public ReagentGuideChangeset Changeset;
+
+ public ReagentGuideRegistryChangedEvent(ReagentGuideChangeset changeset)
+ {
+ Changeset = changeset;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed class ReagentGuideChangeset
+{
+ public Dictionary GuideEntries;
+
+ public HashSet Removed;
+
+ public ReagentGuideChangeset(Dictionary guideEntries, HashSet removed)
+ {
+ GuideEntries = guideEntries;
+ Removed = removed;
+ }
+}
diff --git a/Content.Shared/Localizations/ContentLocalizationManager.cs b/Content.Shared/Localizations/ContentLocalizationManager.cs
index c3069ccd4d..f974eb0785 100644
--- a/Content.Shared/Localizations/ContentLocalizationManager.cs
+++ b/Content.Shared/Localizations/ContentLocalizationManager.cs
@@ -1,4 +1,7 @@
using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Robust.Shared.Utility;
namespace Content.Shared.Localizations
{
@@ -31,13 +34,96 @@ namespace Content.Shared.Localizations
_loc.AddFunction(culture, "UNITS", FormatUnits);
_loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args));
_loc.AddFunction(culture, "LOC", FormatLoc);
+ _loc.AddFunction(culture, "NATURALFIXED", FormatNaturalFixed);
+ _loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent);
+
+
+ /*
+ * The following language functions are specific to the english localization. When working on your own
+ * localization you should NOT modify these, instead add new functions specific to your language/culture.
+ * This ensures the english translations continue to work as expected when fallbacks are needed.
+ */
+ var cultureEn = new CultureInfo("en-US");
+
+ _loc.AddFunction(cultureEn, "MAKEPLURAL", FormatMakePlural);
+ _loc.AddFunction(cultureEn, "MANY", FormatMany);
+ }
+
+ private ILocValue FormatMany(LocArgs args)
+ {
+ var count = ((LocValueNumber) args.Args[1]).Value;
+
+ if (Math.Abs(count - 1) < 0.0001f)
+ {
+ return (LocValueString) args.Args[0];
+ }
+ else
+ {
+ return (LocValueString) FormatMakePlural(args);
+ }
+ }
+
+ private ILocValue FormatNaturalPercent(LocArgs args)
+ {
+ var number = ((LocValueNumber) args.Args[0]).Value * 100;
+ var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
+ var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
+ formatter.NumberDecimalDigits = maxDecimals;
+ return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.') + "%");
+ }
+
+ private ILocValue FormatNaturalFixed(LocArgs args)
+ {
+ var number = ((LocValueNumber) args.Args[0]).Value;
+ var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value);
+ var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone();
+ formatter.NumberDecimalDigits = maxDecimals;
+ return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.'));
+ }
+
+ private static readonly Regex PluralEsRule = new("^.*(s|sh|ch|x|z)$");
+
+ private ILocValue FormatMakePlural(LocArgs args)
+ {
+ var text = ((LocValueString) args.Args[0]).Value;
+ var split = text.Split(" ", 1);
+ var firstWord = split[0];
+ if (PluralEsRule.IsMatch(firstWord))
+ {
+ if (split.Length == 1)
+ return new LocValueString($"{firstWord}es");
+ else
+ return new LocValueString($"{firstWord}es {split[1]}");
+ }
+ else
+ {
+ if (split.Length == 1)
+ return new LocValueString($"{firstWord}s");
+ else
+ return new LocValueString($"{firstWord}s {split[1]}");
+ }
+ }
+
+ // TODO: allow fluent to take in lists of strings so this can be a format function like it should be.
+ ///
+ /// Formats a list as per english grammar rules.
+ ///
+ public static string FormatList(List list)
+ {
+ return list.Count switch
+ {
+ <= 0 => string.Empty,
+ 1 => list[0],
+ 2 => $"{list[0]} and {list[1]}",
+ > 2 => $"{string.Join(", ", list.GetRange(0, list.Count - 2))}, and {list[^1]}"
+ };
}
private static ILocValue FormatLoc(LocArgs args)
{
- var id = ((LocValueString)args.Args[0]).Value;
+ var id = ((LocValueString) args.Args[0]).Value;
- return new LocValueString(Loc.GetString(id));
+ return new LocValueString(Loc.GetString(id, args.Options.Select(x => (x.Key, x.Value.Value!)).ToArray()));
}
private static ILocValue FormatToString(CultureInfo culture, LocArgs args)
@@ -115,8 +201,8 @@ namespace Content.Shared.Localizations
//
// Note that the closing brace isn't replaced so that format specifiers can be applied.
var res = String.Format(
- fmtstr.Replace("{UNIT", "{" + $"{fargs.Length - 1}"),
- fargs
+ fmtstr.Replace("{UNIT", "{" + $"{fargs.Length - 1}"),
+ fargs
);
return new LocValueString(res);
diff --git a/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl b/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl
new file mode 100644
index 0000000000..807b5591a8
--- /dev/null
+++ b/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl
@@ -0,0 +1,50 @@
+reagent-effect-condition-guidebook-total-damage =
+ { $max ->
+ [2147483648] it has at least {NATURALFIXED($min, 2)} total damage
+ *[other] { $min ->
+ [0] it has at most {NATURALFIXED($max, 2)} total damage
+ *[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} total damage
+ }
+ }
+
+reagent-effect-condition-guidebook-reagent-threshold =
+ { $max ->
+ [2147483648] there's at least {NATURALFIXED($min, 2)}u of {$reagent}
+ *[other] { $min ->
+ [0] there's at most {NATURALFIXED($max, 2)}u of {$reagent}
+ *[other] there's between {NATURALFIXED($min, 2)}u and {NATURALFIXED($max, 2)}u of {$reagent}
+ }
+ }
+
+reagent-effect-condition-guidebook-mob-state-condition =
+ the mob is { $state }
+
+reagent-effect-condition-guidebook-solution-temperature =
+ the solution's temperature is { $max ->
+ [2147483648] at least {NATURALFIXED($min, 2)}k
+ *[other] { $min ->
+ [0] at most {NATURALFIXED($max, 2)}k
+ *[other] between {NATURALFIXED($min, 2)}k and {NATURALFIXED($max, 2)}k
+ }
+ }
+
+reagent-effect-condition-guidebook-body-temperature =
+ the body's temperature is { $max ->
+ [2147483648] at least {NATURALFIXED($min, 2)}k
+ *[other] { $min ->
+ [0] at most {NATURALFIXED($max, 2)}k
+ *[other] between {NATURALFIXED($min, 2)}k and {NATURALFIXED($max, 2)}k
+ }
+ }
+
+reagent-effect-condition-guidebook-organ-type =
+ the metabolizing organ { $shouldhave ->
+ [true] is
+ *[false] is not
+ } {INDEFINITE($name)} {$name} organ
+
+reagent-effect-condition-guidebook-has-tag =
+ the target { $invert ->
+ [true] does not have
+ *[false] has
+ } the tag {$tag}
diff --git a/Resources/Locale/en-US/guidebook/chemistry/core.ftl b/Resources/Locale/en-US/guidebook/chemistry/core.ftl
new file mode 100644
index 0000000000..185595826d
--- /dev/null
+++ b/Resources/Locale/en-US/guidebook/chemistry/core.ftl
@@ -0,0 +1,16 @@
+guidebook-reagent-effect-description =
+ {$chance ->
+ [1] { $effect }
+ *[other] Has a { NATURALPERCENT($chance, 2) } chance to { $effect }
+ }{ $conditionCount ->
+ [0] .
+ *[other] {" "}when { $conditions }.
+ }
+
+guidebook-reagent-name = [bold][color={$color}]{CAPITALIZE($name)}[/color][/bold]
+guidebook-reagent-recipes-header = Recipe
+guidebook-reagent-recipes-reagent-display = [bold]{$reagent}[/bold] \[{$ratio}\]
+guidebook-reagent-recipes-mix = Mix
+guidebook-reagent-effects-header = Effects
+guidebook-reagent-effects-metabolism-group-rate = [bold]{$group}[/bold] [color=gray]({$rate} units per second)[/color]
+guidebook-reagent-physical-description = Seems to be {$description}.
diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl
new file mode 100644
index 0000000000..e004cc1590
--- /dev/null
+++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl
@@ -0,0 +1,316 @@
+-create-3rd-person =
+ { $chance ->
+ [1] Creates
+ *[other] create
+ }
+
+-cause-3rd-person =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ }
+
+-satiate-3rd-person =
+ { $chance ->
+ [1] Satiates
+ *[other] satiate
+ }
+
+reagent-effect-guidebook-create-entity-reaction-effect =
+ { $chance ->
+ [1] Creates
+ *[other] create
+ } { $amount ->
+ [1] {INDEFINITE($entname)}
+ *[other] {$amount} {MAKEPLURAL($entname)}
+ }
+
+reagent-effect-guidebook-explosion-reaction-effect =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } an explosion
+
+reagent-effect-guidebook-foam-area-reaction-effect =
+ { $chance ->
+ [1] Creates
+ *[other] create
+ } large quantities of foam
+
+reagent-effect-guidebook-foam-area-reaction-effect =
+ { $chance ->
+ [1] Creates
+ *[other] create
+ } large quantities of smoke
+
+reagent-effect-guidebook-satiate-thirst =
+ { $chance ->
+ [1] Satiates
+ *[other] satiate
+ } { $relative ->
+ [1] thirst averagely
+ *[other] thirst at {NATURALFIXED($relative, 3)}x the average rate
+ }
+
+reagent-effect-guidebook-satiate-hunger =
+ { $chance ->
+ [1] Satiates
+ *[other] satiate
+ } { $relative ->
+ [1] hunger averagely
+ *[other] hunger at {NATURALFIXED($relative, 3)}x the average rate
+ }
+
+reagent-effect-guidebook-health-change =
+ { $chance ->
+ [1] { $healsordeals ->
+ [heals] Heals
+ [deals] Deals
+ *[both] Modifies health by
+ }
+ *[other] { $healsordeals ->
+ [heals] heal
+ [deals] deal
+ *[both] modify health by
+ }
+ } { $changes }
+
+reagent-effect-guidebook-status-effect =
+ { $type ->
+ [add] { $chance ->
+ [1] Causes
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
+ *[set] { $chance ->
+ [1] Causes
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+ [remove]{ $chance ->
+ [1] Removes
+ *[other] remove
+ } {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
+ }
+
+reagent-effect-guidebook-activate-artifact =
+ { $chance ->
+ [1] Attempts
+ *[other] attempt
+ } to activate an artifact
+
+reagent-effect-guidebook-set-solution-temperature-effect =
+ { $chance ->
+ [1] Sets
+ *[other] set
+ } the solution temperature to exactly {NATURALFIXED($temperature, 2)}k
+
+reagent-effect-guidebook-adjust-solution-temperature-effect =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Adds
+ *[-1] Removes
+ }
+ *[other]
+ { $deltasign ->
+ [1] add
+ *[-1] remove
+ }
+ } heat from the solution until it reaches { $deltasign ->
+ [1] at most {NATURALFIXED($maxtemp, 2)}k
+ *[-1] at least {NATURALFIXED($mintemp, 2)}k
+ }
+
+reagent-effect-guidebook-adjust-reagent-reagent =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Adds
+ *[-1] Removes
+ }
+ *[other]
+ { $deltasign ->
+ [1] add
+ *[-1] remove
+ }
+ } {NATURALFIXED($amount, 2)}u of {$reagent} { $deltasign ->
+ [1] to
+ *[-1] from
+ } the solution
+
+reagent-effect-guidebook-adjust-reagent-group =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Adds
+ *[-1] Removes
+ }
+ *[other]
+ { $deltasign ->
+ [1] add
+ *[-1] remove
+ }
+ } {NATURALFIXED($amount, 2)}u of reagents in the group {$group} { $deltasign ->
+ [1] to
+ *[-1] from
+ } the solution
+
+reagent-effect-guidebook-adjust-temperature =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Adds
+ *[-1] Removes
+ }
+ *[other]
+ { $deltasign ->
+ [1] add
+ *[-1] remove
+ }
+ } {POWERJOULES($amount)} of heat { $deltasign ->
+ [1] to
+ *[-1] from
+ } the body it's in
+
+reagent-effect-guidebook-chem-cause-disease =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } the disease { $disease }
+
+reagent-effect-guidebook-chem-cause-random-disease =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } the diseases { $diseases }
+
+reagent-effect-guidebook-jittering =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } jittering
+
+reagent-effect-guidebook-chem-clean-bloodstream =
+ { $chance ->
+ [1] Cleanses
+ *[other] cleanse
+ } the bloodstream of other chemicals
+
+reagent-effect-guidebook-cure-disease =
+ { $chance ->
+ [1] Cures
+ *[other] cure
+ } diseases
+
+reagent-effect-guidebook-cure-eye-damage =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Heals
+ *[-1] Deals
+ }
+ *[other]
+ { $deltasign ->
+ [1] heal
+ *[-1] deal
+ }
+ } eye damage
+
+reagent-effect-guidebook-chem-vomit =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } vomiting
+
+reagent-effect-guidebook-create-gas =
+ { $chance ->
+ [1] Creates
+ *[other] create
+ } { $moles } { $moles ->
+ [1] mole
+ *[other] moles
+ } of { $gas }
+
+reagent-effect-guidebook-drunk =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } drunkness
+
+reagent-effect-guidebook-electrocute =
+ { $chance ->
+ [1] Electrocutes
+ *[other] electrocute
+ } the metabolizer for {NATURALFIXED($time, 3)} {MANY("second", $time)}
+
+reagent-effect-guidebook-extinguish-reaction =
+ { $chance ->
+ [1] Extinguishes
+ *[other] extinguish
+ } fire
+
+reagent-effect-guidebook-flammable-reaction =
+ { $chance ->
+ [1] Increases
+ *[other] increase
+ } flammability
+
+reagent-effect-guidebook-ignite =
+ { $chance ->
+ [1] Ignites
+ *[other] ignite
+ } the metabolizer
+
+reagent-effect-guidebook-make-sentient =
+ { $chance ->
+ [1] Makes
+ *[other] make
+ } the metabolizer sentient
+
+reagent-effect-guidebook-modify-bleed-amount =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Induces
+ *[-1] Reduces
+ }
+ *[other] { $deltasign ->
+ [1] induce
+ *[-1] reduce
+ }
+ } bleeding
+
+reagent-effect-guidebook-modify-blood-level =
+ { $chance ->
+ [1] { $deltasign ->
+ [1] Increases
+ *[-1] Decreases
+ }
+ *[other] { $deltasign ->
+ [1] increases
+ *[-1] decreases
+ }
+ } blood level
+
+reagent-effect-guidebook-paralyze =
+ { $chance ->
+ [1] Paralyzes
+ *[other] paralyze
+ } the metabolizer for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
+
+reagent-effect-guidebook-movespeed-modifier =
+ { $chance ->
+ [1] Modifies
+ *[other] modify
+ } movement speed by {NATURALFIXED($walkspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
+
+reagent-effect-guidebook-reset-narcolepsy =
+ { $chance ->
+ [1] Temporarily staves
+ *[other] temporarily stave
+ } off narcolepsy
+
+reagent-effect-guidebook-wash-cream-pie-reaction =
+ { $chance ->
+ [1] Washes
+ *[other] wash
+ } off cream pie from one's face
+
+reagent-effect-guidebook-missing =
+ { $chance ->
+ [1] Causes
+ *[other] cause
+ } an unknown effect as nobody has written this effect yet
diff --git a/Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl b/Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl
new file mode 100644
index 0000000000..d5eba5b02c
--- /dev/null
+++ b/Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl
@@ -0,0 +1,5 @@
+health-change-display =
+ { $deltasign ->
+ [-1] [color=green]{NATURALFIXED($amount, 2)}[/color] {$kind}
+ *[1] [color=red]{NATURALFIXED($amount, 2)}[/color] {$kind}
+ }
diff --git a/Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl b/Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl
new file mode 100644
index 0000000000..8cedbcabb6
--- /dev/null
+++ b/Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl
@@ -0,0 +1,11 @@
+reagent-effect-status-effect-Stun = stunning
+reagent-effect-status-effect-KnockedDown = knockdown
+reagent-effect-status-effect-Jitter = jittering
+reagent-effect-status-effect-TemporaryBlindness = blindess
+reagent-effect-status-effect-SeeingRainbows = hallucinations
+reagent-effect-status-effect-Muted = inability to speak
+reagent-effect-status-effect-Stutter = stuttering
+reagent-effect-status-effect-ForcedSleep = unconsciousness
+reagent-effect-status-effect-Drunk = drunkness
+reagent-effect-status-effect-PressureImmunity = pressure immunity
+reagent-effect-status-effect-Pacified = combat pacification
diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl
index 5a688dd565..805eac357d 100644
--- a/Resources/Locale/en-US/guidebook/guides.ftl
+++ b/Resources/Locale/en-US/guidebook/guides.ftl
@@ -14,6 +14,7 @@ guide-entry-radio = Radio
guide-entry-jobs = Jobs
guide-entry-salvage = Salvage
guide-entry-survival = Survival
+guide-entry-chemicals = Chemicals
guide-entry-ss14 = Space Station 14
guide-entry-janitorial = Janitorial
diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml
index 11242bb7b8..1aa19f6084 100644
--- a/Resources/Prototypes/Chemistry/metabolizer_types.yml
+++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml
@@ -3,24 +3,32 @@
- type: metabolizerType
id: Animal
+ name: animal
- type: metabolizerType
id: Dragon
+ name: dragon
- type: metabolizerType
id: Human
+ name: human
- type: metabolizerType
id: Slime
+ name: slime
- type: metabolizerType
id: Vox
+ name: vox
- type: metabolizerType
id: Rat
+ name: rat
- type: metabolizerType
id: Plant
+ name: plant
- type: metabolizerType
id: Dwarf
+ name: dwarf
diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
index 44babe2411..27a2cd53be 100644
--- a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
+++ b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
@@ -32,3 +32,6 @@
- type: UpgradePowerDraw
powerDrawMultiplier: 0.75
scaling: Exponential
+ - type: GuideHelp
+ guides:
+ - Chemicals
diff --git a/Resources/Prototypes/Guidebook/shiftandcrew.yml b/Resources/Prototypes/Guidebook/shiftandcrew.yml
index 39e580e052..fc37b330bf 100644
--- a/Resources/Prototypes/Guidebook/shiftandcrew.yml
+++ b/Resources/Prototypes/Guidebook/shiftandcrew.yml
@@ -15,6 +15,11 @@
name: guide-entry-survival
text: "/ServerInfo/Guidebook/Survival.xml"
+- type: guideEntry
+ id: Chemicals
+ name: guide-entry-chemicals
+ text: "/ServerInfo/Guidebook/Chemicals.xml"
+
- type: guideEntry
id: Janitorial
name: guide-entry-janitorial
diff --git a/Resources/Prototypes/Guidebook/ss14.yml b/Resources/Prototypes/Guidebook/ss14.yml
index bb98b11fcf..bcf474b773 100644
--- a/Resources/Prototypes/Guidebook/ss14.yml
+++ b/Resources/Prototypes/Guidebook/ss14.yml
@@ -6,3 +6,4 @@
- Controls
- Jobs
- Survival
+ - Chemicals
diff --git a/Resources/ServerInfo/Guidebook/Chemicals.xml b/Resources/ServerInfo/Guidebook/Chemicals.xml
new file mode 100644
index 0000000000..e157dbb437
--- /dev/null
+++ b/Resources/ServerInfo/Guidebook/Chemicals.xml
@@ -0,0 +1,34 @@
+
+# Chemicals
+
+Chemicals are a powerful tool that can cause a variety of effects when consumed. Some can be found in plants, purchased from cargo, or be synthesized through combination with other chemicals.
+
+Knowing different types of chemicals and their effects is important for being able to manage injury and danger.
+
+## Elements
+
+
+## Medicine
+
+
+## Narcotics
+
+
+## Pyrotechnics
+
+
+## Toxins
+
+
+## Foods
+
+
+## Botanical
+
+
+## Biological
+
+
+## Other
+
+
diff --git a/Resources/Textures/Interface/Misc/beakerlarge.png b/Resources/Textures/Interface/Misc/beakerlarge.png
new file mode 100644
index 0000000000..4e0fb0fc06
Binary files /dev/null and b/Resources/Textures/Interface/Misc/beakerlarge.png differ
diff --git a/RobustToolbox b/RobustToolbox
index e2830c9ad8..7a04c81fe1 160000
--- a/RobustToolbox
+++ b/RobustToolbox
@@ -1 +1 @@
-Subproject commit e2830c9ad8820343a06ed9fb7297a71ca769e57a
+Subproject commit 7a04c81fe170f87e20e983e810afd601c3f9e956