From efb3bca1c55ff815028300d9e506d0cfc3f07850 Mon Sep 17 00:00:00 2001
From: Whatstone <166147148+whatston3@users.noreply.github.com>
Date: Sat, 26 Oct 2024 17:37:43 -0400
Subject: [PATCH] Medical recipe guidebook entries (#2331)
---
.../Controls/GuideMedicalComposition.xaml | 21 ++
.../Controls/GuideMedicalComposition.xaml.cs | 32 +++
.../Controls/GuideMedicalDamage.xaml | 21 ++
.../Controls/GuideMedicalDamage.xaml.cs | 40 ++++
.../Guidebook/Controls/GuideMedicalEmbed.xaml | 53 +++++
.../Controls/GuideMedicalEmbed.xaml.cs | 188 ++++++++++++++++++
.../Controls/GuideMedicalGroupEmbed.xaml | 4 +
.../Controls/GuideMedicalGroupEmbed.xaml.cs | 36 ++++
.../Controls/GuideMedicalSource.xaml | 36 ++++
.../Controls/GuideMedicalSource.xaml.cs | 96 +++++++++
.../EntitySystems/MedicalGuideDataSystem.cs | 30 +++
.../EntitySystems/MedicalRecipeDataSystem.cs | 88 ++++++++
.../Medical/SharedMedicalGuideDataSystem.cs | 81 ++++++++
.../Locale/en-US/_NF/guidebook/guides.ftl | 3 +
.../Locale/en-US/_NF/guidebook/medical.ftl | 4 +
Resources/Prototypes/Guidebook/references.yml | 3 +-
.../Prototypes/_NF/Guidebook/references.yml | 5 +
.../_EE/Guidebook/Service/FoodRecipes.xml | 2 +-
.../_NF/Guidebook/Medical/MedicalRecipes.xml | 8 +
19 files changed, 748 insertions(+), 3 deletions(-)
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml.cs
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml.cs
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml.cs
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml.cs
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml
create mode 100644 Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml.cs
create mode 100644 Content.Client/_NF/Medical/EntitySystems/MedicalGuideDataSystem.cs
create mode 100644 Content.Server/_NF/Medical/EntitySystems/MedicalRecipeDataSystem.cs
create mode 100644 Content.Shared/_NF/Medical/SharedMedicalGuideDataSystem.cs
create mode 100644 Resources/Locale/en-US/_NF/guidebook/medical.ftl
create mode 100644 Resources/Prototypes/_NF/Guidebook/references.yml
create mode 100644 Resources/ServerInfo/_NF/Guidebook/Medical/MedicalRecipes.xml
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml b/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml
new file mode 100644
index 00000000000..87b755d6f1a
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml.cs b/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml.cs
new file mode 100644
index 00000000000..fd44847e355
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalComposition.xaml.cs
@@ -0,0 +1,32 @@
+using Content.Client.Guidebook.Controls;
+using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._NF.Guidebook.Controls; // Frontier: add _EE
+
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMedicalComposition : BoxContainer, ISearchableControl
+{
+ public GuideMedicalComposition(ReagentPrototype proto, FixedPoint2 quantity)
+ {
+ RobustXamlLoader.Load(this);
+
+ ReagentLabel.Text = proto.LocalizedName;
+ AmountLabel.Text = quantity.ToString();
+ }
+
+ public bool CheckMatchesSearch(string query)
+ {
+ return this.ChildrenContainText(query);
+ }
+
+ public void SetHiddenState(bool state, string query)
+ {
+ Visible = CheckMatchesSearch(query) ? state : !state;
+ }
+}
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml b/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml
new file mode 100644
index 00000000000..d8ef86c56e1
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml.cs b/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml.cs
new file mode 100644
index 00000000000..8f8dcefa64a
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalDamage.xaml.cs
@@ -0,0 +1,40 @@
+using Content.Client.Guidebook.Controls;
+using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._NF.Guidebook.Controls;
+
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMedicalDamage : BoxContainer, ISearchableControl
+{
+ public GuideMedicalDamage(DamageTypePrototype proto, FixedPoint2 quantity)
+ {
+ RobustXamlLoader.Load(this);
+
+ DamageLabel.Text = proto.LocalizedName;
+ AmountLabel.Text = quantity.ToString();
+ }
+
+ public GuideMedicalDamage(DamageGroupPrototype proto, FixedPoint2 quantity)
+ {
+ RobustXamlLoader.Load(this);
+
+ DamageLabel.Text = Loc.GetString("guidebook-medical-damage-group", ("name", proto.LocalizedName));
+ AmountLabel.Text = quantity.ToString();
+ }
+
+ public bool CheckMatchesSearch(string query)
+ {
+ return this.ChildrenContainText(query);
+ }
+
+ public void SetHiddenState(bool state, string query)
+ {
+ Visible = CheckMatchesSearch(query) ? state : !state;
+ }
+}
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml b/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml
new file mode 100644
index 00000000000..a6f64498269
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml.cs b/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml.cs
new file mode 100644
index 00000000000..2016deb4200
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalEmbed.xaml.cs
@@ -0,0 +1,188 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client._NF.Medical.EntitySystems;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Guidebook.Controls;
+using Content.Client.Guidebook.Richtext;
+using Content.Client.Message;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+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._NF.Guidebook.Controls;
+
+///
+/// Control for embedding a medical recipe into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMedicalEmbed : BoxContainer, IDocumentTag, ISearchableControl
+{
+ [Dependency] private readonly IEntitySystemManager _systemManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ private readonly MedicalGuideDataSystem _medicalGuideData;
+ private readonly ISawmill _logger = default!;
+
+ public GuideMedicalEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _medicalGuideData = _systemManager.GetEntitySystem();
+ _logger = Logger.GetSawmill("medical guide");
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideMedicalEmbed(MedicalGuideEntry entry) : this()
+ {
+ GenerateControl(entry);
+ }
+
+ public bool CheckMatchesSearch(string query)
+ {
+ return ResultName.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true
+ || Description.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true;
+ }
+
+ public void SetHiddenState(bool state, string query)
+ {
+ Visible = CheckMatchesSearch(query) ? state : !state;
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Result", out var id))
+ {
+ _logger.Error("Result embed tag is missing food prototype argument.");
+ return false;
+ }
+
+ if (!_medicalGuideData.TryGetData(id, out var data))
+ {
+ _logger.Warning($"Specified result prototype \"{id}\" does not have any known sources.");
+ return false;
+ }
+
+ GenerateControl(data);
+
+ control = this;
+ return true;
+ }
+
+ private void GenerateControl(MedicalGuideEntry data)
+ {
+ _prototype.TryIndex(data.Result, out var proto);
+ if (proto == null)
+ {
+ ResultName.SetMarkup(Loc.GetString("guidebook-food-unknown-proto", ("id", data.Result)));
+ return;
+ }
+
+ var composition = data.Composition
+ .Select(it => _prototype.TryIndex(it.Reagent.Prototype, out var reagent) ? (reagent, it.Quantity) : (null, 0))
+ .Where(it => it.reagent is not null)
+ .Cast<(ReagentPrototype, FixedPoint2)>()
+ .ToList();
+
+ #region Colors
+
+ CalculateColors(composition, out var textColor, out var backgroundColor);
+
+ NameBackground.PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = backgroundColor
+ };
+ ResultName.SetMarkup(Loc.GetString("guidebook-food-name", ("color", textColor), ("name", proto.Name)));
+
+ #endregion
+
+ #region Recipes
+ if (data.Recipes.Length > 0)
+ RecipesContainer.Visible = true;
+
+ foreach (var recipe in data.Recipes.OrderBy(it => it.OutputCount))
+ {
+ var control = new GuideMedicalSource(proto, recipe, _prototype);
+ RecipesDescriptionContainer.AddChild(control);
+ }
+
+ #endregion
+
+ #region Composition
+ if (composition.Count > 0)
+ CompositionContainer.Visible = true;
+
+ foreach (var (reagent, quantity) in composition)
+ {
+ var control = new GuideMedicalComposition(reagent, quantity);
+ CompositionDescriptionContainer.AddChild(control);
+ }
+
+ #endregion
+
+ #region Damage
+ var damageDict = data.Healing?.DamageDict ?? new();
+ if (damageDict.Count > 0)
+ DamageContainer.Visible = true;
+
+ foreach (var (damageType, damage) in damageDict)
+ {
+ if (_prototype.TryIndex(damageType, out var damageProto))
+ {
+ var control = new GuideMedicalDamage(damageProto, -damage); // Negative damage means positive healing
+ DamageDescriptionContainer.AddChild(control);
+ }
+ else if (_prototype.TryIndex(damageType, out var groupProto))
+ {
+ var control = new GuideMedicalDamage(groupProto, -damage); // Negative damage means positive healing
+ DamageDescriptionContainer.AddChild(control);
+ }
+ }
+
+ #endregion
+
+ FormattedMessage description = new();
+ description.AddText(proto?.Description ?? string.Empty);
+ // Cannot describe food flavor or smth beause food is entirely server-side
+
+ Description.SetMessage(description);
+ }
+
+ private void CalculateColors(List<(ReagentPrototype, FixedPoint2)> composition, out Color text, out Color background)
+ {
+ // Background color is calculated as the weighted average of the colors of the composition.
+ // Text color is determined based on background luminosity.
+ float r = 0, g = 0, b = 0;
+ FixedPoint2 weight = 0;
+
+ foreach (var (proto, quantity) in composition)
+ {
+ var tcolor = proto.SubstanceColor;
+ var prevalence =
+ quantity <= 0 ? 0f
+ : weight == 0f ? 1f
+ : (quantity / (weight + quantity)).Float();
+
+ r = r * (1 - prevalence) + tcolor.R * prevalence;
+ g = g * (1 - prevalence) + tcolor.G * prevalence;
+ b = b * (1 - prevalence) + tcolor.B * prevalence;
+
+ if (quantity > 0)
+ weight += quantity;
+ }
+
+ // Copied from GuideReagentEmbed which was probably copied from stackoverflow. This is the formula for color luminosity.
+ var lum = 0.2126f * r + 0.7152f * g + 0.0722f;
+
+ background = new Color(r, g, b);
+ text = lum > 0.5f ? Color.Black : Color.White;
+ }
+}
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml b/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml
new file mode 100644
index 00000000000..6551f2cf18c
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml.cs b/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml.cs
new file mode 100644
index 00000000000..134e51ff544
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalGroupEmbed.xaml.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client._NF.Medical.EntitySystems;
+using Content.Client.Guidebook.Richtext;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._NF.Guidebook.Controls;
+
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMedicalGroupEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IEntitySystemManager _sysMan = default!;
+
+ public GuideMedicalGroupEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ MouseFilter = MouseFilterMode.Stop;
+
+ foreach (var data in _sysMan.GetEntitySystem().Registry.OrderBy(it => it.Identifier))
+ {
+ var embed = new GuideMedicalEmbed(data);
+ GroupContainer.AddChild(embed);
+ }
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = this;
+ return true;
+ }
+}
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml b/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml
new file mode 100644
index 00000000000..4527225ee4c
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml.cs b/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml.cs
new file mode 100644
index 00000000000..eb8954af8b5
--- /dev/null
+++ b/Content.Client/_NF/Guidebook/Controls/GuideMedicalSource.xaml.cs
@@ -0,0 +1,96 @@
+using System.Linq;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Guidebook.Controls;
+using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Content.Shared.Kitchen;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client._NF.Guidebook.Controls;
+
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideMedicalSource : BoxContainer, ISearchableControl
+{
+ private readonly IPrototypeManager _protoMan;
+ private readonly SpriteSystem _sprites = default!;
+
+ public GuideMedicalSource(IPrototypeManager protoMan)
+ {
+ RobustXamlLoader.Load(this);
+ _protoMan = protoMan;
+ _sprites = IoCManager.Resolve().GetEntitySystem();
+ }
+
+ public GuideMedicalSource(EntityPrototype result, MedicalRecipeData entry, IPrototypeManager protoMan) : this(protoMan)
+ {
+ GenerateControl(entry);
+
+ GenerateOutputs(result, entry);
+ }
+
+ private void GenerateControl(MedicalRecipeData entry)
+ {
+ if (!_protoMan.TryIndex(entry.Recipe, out var recipe))
+ {
+ SourceLabel.Text = Loc.GetString("guidebook-food-unknown-proto", ("id", entry.Result)); // Frontier: SetMessage _protoMan.TryIndex(it.Key, out var proto) ? FormatIngredient(proto, it.Value) : "")
+ .Where(it => it.Length > 0);
+ var combinedLiquids = recipe.IngredientsReagents
+ .Select(it => _protoMan.TryIndex(it.Key, out var proto) ? FormatIngredient(proto, it.Value) : "")
+ .Where(it => it.Length > 0);
+
+ var combinedIngredients = string.Join("\n", combinedLiquids.Union(combinedSolids));
+ SourceLabel.Text = Loc.GetString("guidebook-food-processing-recipe", ("ingredients", combinedIngredients)); // Frontier: SetMessage(OnReceiveRegistryUpdate);
+ }
+
+ private void OnReceiveRegistryUpdate(MedicalGuideRegistryChangedEvent message)
+ {
+ Registry = message.Changeset;
+ }
+
+ public bool TryGetData(EntProtoId result, out MedicalGuideEntry entry)
+ {
+ var index = Registry.FindIndex(it => it.Result == result);
+ if (index == -1)
+ {
+ entry = default;
+ return false;
+ }
+
+ entry = Registry[index];
+ return true;
+ }
+}
diff --git a/Content.Server/_NF/Medical/EntitySystems/MedicalRecipeDataSystem.cs b/Content.Server/_NF/Medical/EntitySystems/MedicalRecipeDataSystem.cs
new file mode 100644
index 00000000000..36c420c2403
--- /dev/null
+++ b/Content.Server/_NF/Medical/EntitySystems/MedicalRecipeDataSystem.cs
@@ -0,0 +1,88 @@
+using System.Linq;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Server.Medical.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Damage;
+using Content.Shared.Kitchen;
+using Robust.Server.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server._NF.Medical.EntitySystems;
+
+public sealed class MedicalRecipeDataSystem : SharedMedicalGuideDataSystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
+ [Dependency] private readonly IComponentFactory _componentFactory = default!;
+
+ private Dictionary> _sources = new();
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnPrototypesReloaded);
+ _player.PlayerStatusChanged += OnPlayerStatusChanged;
+
+ ReloadRecipes();
+ }
+
+ private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
+ {
+ if (!args.WasModified()
+ && !args.WasModified()
+ )
+ return;
+
+ ReloadRecipes();
+ }
+
+ public void ReloadRecipes()
+ {
+ // TODO: add this code to the list of known recipes because this is spaghetti
+ _sources.Clear();
+
+ // Recipes
+ foreach (var recipe in _protoMan.EnumeratePrototypes())
+ {
+ MicrowaveRecipeType recipeType = (MicrowaveRecipeType)recipe.RecipeType;
+ if (recipeType.HasFlag(MicrowaveRecipeType.MedicalAssembler))
+ {
+ _sources.GetOrNew(recipe.Result).Add(new MedicalRecipeData(recipe));
+ }
+ }
+
+ Registry.Clear();
+
+ foreach (var (result, sources) in _sources)
+ {
+ var proto = _protoMan.Index(result);
+ ReagentQuantity[] reagents = [];
+ // Hack: assume
+ if (proto.TryGetComponent(out var manager, _componentFactory))
+ reagents = manager?.Solutions?.FirstOrNull()?.Value?.Contents?.ToArray() ?? [];
+
+ DamageSpecifier? damage = null;
+ if (proto.TryGetComponent(out var healing, _componentFactory))
+ damage = healing.Damage;
+
+ // Limit the number of sources to 10 - shouldn't be an issue for medical recipes, but just in case.
+ var distinctSources = sources.DistinctBy(it => it.Identitier).Take(10);
+
+ var entry = new MedicalGuideEntry(result, proto.Name, distinctSources.ToArray(), reagents, damage);
+ Registry.Add(entry);
+ }
+
+ RaiseNetworkEvent(new MedicalGuideRegistryChangedEvent(Registry));
+ }
+
+ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
+ {
+ if (args.NewStatus != SessionStatus.Connected)
+ return;
+
+ RaiseNetworkEvent(new MedicalGuideRegistryChangedEvent(Registry), args.Session);
+ }
+}
diff --git a/Content.Shared/_NF/Medical/SharedMedicalGuideDataSystem.cs b/Content.Shared/_NF/Medical/SharedMedicalGuideDataSystem.cs
new file mode 100644
index 00000000000..adefdf8f7bb
--- /dev/null
+++ b/Content.Shared/_NF/Medical/SharedMedicalGuideDataSystem.cs
@@ -0,0 +1,81 @@
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Damage;
+using Content.Shared.Kitchen;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+// A clone of the FoodGuideDataSystem. Thank you to Mnemotechnician for the original implementation.
+// Redundancy.
+public abstract class SharedMedicalGuideDataSystem : EntitySystem
+{
+ public List Registry = new();
+}
+
+[Serializable, NetSerializable]
+public sealed class MedicalGuideRegistryChangedEvent : EntityEventArgs
+{
+ [DataField]
+ public List Changeset;
+
+ public MedicalGuideRegistryChangedEvent(List changeset)
+ {
+ Changeset = changeset;
+ }
+}
+
+[DataDefinition, Serializable, NetSerializable]
+public partial struct MedicalGuideEntry
+{
+ [DataField]
+ public EntProtoId Result;
+
+ [DataField]
+ public string Identifier; // Used for sorting
+
+ [DataField]
+ public MedicalRecipeData[] Recipes;
+
+ [DataField]
+ public ReagentQuantity[] Composition;
+
+ [DataField]
+ public DamageSpecifier? Healing;
+
+ public MedicalGuideEntry(EntProtoId result, string identifier, MedicalRecipeData[] recipes, ReagentQuantity[] composition, DamageSpecifier? healing)
+ {
+ Result = result;
+ Identifier = identifier;
+ Recipes = recipes;
+ Composition = composition;
+ Healing = healing;
+ }
+}
+
+[Serializable, NetSerializable]
+public sealed partial class MedicalRecipeData
+{
+ [DataField]
+ public ProtoId Recipe;
+
+ [DataField]
+ public EntProtoId Result;
+
+ [DataField]
+ private int _outputCount;
+ public int OutputCount => _outputCount;
+
+ ///
+ /// A string used to distinguish different sources. Typically the name of the related entity.
+ ///
+ public string Identitier;
+
+ public MedicalRecipeData(FoodRecipePrototype proto)
+ {
+ Identitier = proto.Name;
+ Recipe = proto.ID;
+ Result = proto.Result;
+ _outputCount = proto.ResultCount;
+ }
+}
diff --git a/Resources/Locale/en-US/_NF/guidebook/guides.ftl b/Resources/Locale/en-US/_NF/guidebook/guides.ftl
index c538a8dcaf0..9d887b492ae 100644
--- a/Resources/Locale/en-US/_NF/guidebook/guides.ftl
+++ b/Resources/Locale/en-US/_NF/guidebook/guides.ftl
@@ -10,6 +10,9 @@ guide-entry-frontier-rules = Server Rules
# Security entries
guide-entry-nfsd-smuggling = Smuggling
+# Reference entries
+guide-entry-medicalrecipes = Medical Recipes
+
# Expedition faction entries
guide-entry-expedition-aberrant-flesh = Aberrant Flesh
guide-entry-expedition-argocytes = Argocytes
diff --git a/Resources/Locale/en-US/_NF/guidebook/medical.ftl b/Resources/Locale/en-US/_NF/guidebook/medical.ftl
new file mode 100644
index 00000000000..ed5ebf4a937
--- /dev/null
+++ b/Resources/Locale/en-US/_NF/guidebook/medical.ftl
@@ -0,0 +1,4 @@
+guidebook-medical-reagents-header = Active Components
+guidebook-medical-damage-header = Healing
+
+guidebook-medical-damage-group = [color=gold]{$name}[/color]
\ No newline at end of file
diff --git a/Resources/Prototypes/Guidebook/references.yml b/Resources/Prototypes/Guidebook/references.yml
index c0f9cc01ff2..23870676c56 100644
--- a/Resources/Prototypes/Guidebook/references.yml
+++ b/Resources/Prototypes/Guidebook/references.yml
@@ -7,6 +7,7 @@
- Drinks
- FoodRecipes
- Writing
+ - MedicalRecipes # Frontier
- type: guideEntry
id: Drinks
@@ -24,5 +25,3 @@
id: Writing
name: guide-entry-writing
text: "/ServerInfo/Guidebook/Writing.xml"
-
-
diff --git a/Resources/Prototypes/_NF/Guidebook/references.yml b/Resources/Prototypes/_NF/Guidebook/references.yml
new file mode 100644
index 00000000000..2827b8ab0bc
--- /dev/null
+++ b/Resources/Prototypes/_NF/Guidebook/references.yml
@@ -0,0 +1,5 @@
+- type: guideEntry
+ id: MedicalRecipes
+ name: guide-entry-medicalrecipes
+ text: "/ServerInfo/_NF/Guidebook/Medical/MedicalRecipes.xml"
+ filterEnabled: True
\ No newline at end of file
diff --git a/Resources/ServerInfo/_EE/Guidebook/Service/FoodRecipes.xml b/Resources/ServerInfo/_EE/Guidebook/Service/FoodRecipes.xml
index c74b947dbf6..f6a933765df 100644
--- a/Resources/ServerInfo/_EE/Guidebook/Service/FoodRecipes.xml
+++ b/Resources/ServerInfo/_EE/Guidebook/Service/FoodRecipes.xml
@@ -1,5 +1,5 @@
-## Recipe list
+## Recipe List
Note: Only solid foods are listed here! To learn recipes for liquid ingredients, check the chemistry guidebook.
This list is auto-generated and contains all known foods.
diff --git a/Resources/ServerInfo/_NF/Guidebook/Medical/MedicalRecipes.xml b/Resources/ServerInfo/_NF/Guidebook/Medical/MedicalRecipes.xml
new file mode 100644
index 00000000000..37fbe1f45d7
--- /dev/null
+++ b/Resources/ServerInfo/_NF/Guidebook/Medical/MedicalRecipes.xml
@@ -0,0 +1,8 @@
+
+## Medical Recipe List
+
+This list is auto-generated and contains all known recipes for the medical assembler.
+
+
+
+