]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add reagent sources to the guidebook (#22627)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Sun, 31 Dec 2023 07:34:59 +0000 (02:34 -0500)
committerGitHub <noreply@github.com>
Sun, 31 Dec 2023 07:34:59 +0000 (23:34 -0800)
* source in my guidebook

* finish it!

* sir yes sir oorah

* network that bitch, baby

22 files changed:
Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
Content.Client/Guidebook/Controls/GuideReagentReaction.xaml
Content.Client/Guidebook/Controls/GuideReagentReaction.xaml.cs
Content.Server/Kitchen/Components/ExtractableComponent.cs [deleted file]
Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs
Content.Shared/Chemistry/Reaction/ReactionPrototype.cs
Content.Shared/Kitchen/Components/ExtractableComponent.cs [new file with mode: 0644]
Resources/Locale/en-US/chemistry/components/mixing-component.ftl
Resources/Locale/en-US/guidebook/chemistry/core.ftl
Resources/Prototypes/Chemistry/mixing_types.yml
Resources/Prototypes/Entities/Objects/Materials/parts.yml
Resources/Prototypes/Recipes/Reactions/biological.yml
Resources/Prototypes/Recipes/Reactions/chemicals.yml
Resources/Prototypes/Recipes/Reactions/drinks.yml
Resources/Prototypes/Recipes/Reactions/food.yml
Resources/Prototypes/Recipes/Reactions/fun.yml
Resources/Prototypes/Recipes/Reactions/gas.yml
Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml
Resources/Textures/Structures/Piping/Atmospherics/condenser.rsi/display.png [new file with mode: 0644]
Resources/Textures/Structures/Piping/Atmospherics/condenser.rsi/meta.json

index 736e22f834ab9b194ad5d4aeceb596c3be302db1..4a9bd60dcbfe699806063a1f1f0530f72e2a93b7 100644 (file)
@@ -1,16 +1,39 @@
-using Content.Shared.Chemistry;
+using System.Linq;
+using Content.Shared.Atmos.Prototypes;
+using Content.Shared.Body.Part;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Kitchen.Components;
+using Content.Shared.Prototypes;
+using Robust.Shared.Prototypes;
 
 namespace Content.Client.Chemistry.EntitySystems;
 
 /// <inheritdoc/>
 public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
 {
+    [ValidatePrototypeId<MixingCategoryPrototype>]
+    private const string DefaultMixingCategory = "DummyMix";
+    [ValidatePrototypeId<MixingCategoryPrototype>]
+    private const string DefaultGrindCategory = "DummyGrind";
+    [ValidatePrototypeId<MixingCategoryPrototype>]
+    private const string DefaultJuiceCategory = "DummyJuice";
+    [ValidatePrototypeId<MixingCategoryPrototype>]
+    private const string DefaultCondenseCategory = "DummyCondense";
+
+    private readonly Dictionary<string, List<ReagentSourceData>> _reagentSources = new();
+
     /// <inheritdoc/>
     public override void Initialize()
     {
         base.Initialize();
 
         SubscribeNetworkEvent<ReagentGuideRegistryChangedEvent>(OnReceiveRegistryUpdate);
+        SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
+        OnPrototypesReloaded(null);
     }
 
     private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
@@ -26,4 +49,176 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
             Registry[key] = val;
         }
     }
+
+    private void OnPrototypesReloaded(PrototypesReloadedEventArgs? ev)
+    {
+        // this doesn't check what prototypes are being reloaded because, to be frank, we use a lot of them.
+        _reagentSources.Clear();
+        foreach (var reagent in PrototypeManager.EnumeratePrototypes<ReagentPrototype>())
+        {
+            _reagentSources.Add(reagent.ID, new());
+        }
+
+        foreach (var reaction in PrototypeManager.EnumeratePrototypes<ReactionPrototype>())
+        {
+            if (!reaction.Source)
+                continue;
+
+            var data = new ReagentReactionSourceData(
+                reaction.MixingCategories ?? new () { DefaultMixingCategory },
+                reaction);
+            foreach (var product in reaction.Products.Keys)
+            {
+                _reagentSources[product].Add(data);
+            }
+        }
+
+        foreach (var gas in PrototypeManager.EnumeratePrototypes<GasPrototype>())
+        {
+            if (gas.Reagent == null)
+                continue;
+
+            var data = new ReagentGasSourceData(
+                new () { DefaultCondenseCategory },
+                gas);
+            _reagentSources[gas.Reagent].Add(data);
+        }
+
+        // store the names of the entities used so we don't get repeats in the guide.
+        var usedNames = new List<string>();
+        foreach (var entProto in PrototypeManager.EnumeratePrototypes<EntityPrototype>())
+        {
+            if (entProto.Abstract || usedNames.Contains(entProto.Name))
+                continue;
+
+            if (!entProto.TryGetComponent<ExtractableComponent>(out var extractableComponent))
+                continue;
+
+            //these bloat the hell out of blood/fat
+            if (entProto.HasComponent<BodyPartComponent>())
+                continue;
+
+            //these feel obvious...
+            if (entProto.HasComponent<PillComponent>())
+                continue;
+
+            if (extractableComponent.JuiceSolution is { } juiceSolution)
+            {
+                var data = new ReagentEntitySourceData(
+                    new() { DefaultJuiceCategory },
+                    entProto,
+                    juiceSolution);
+                foreach (var (id, _) in juiceSolution.Contents)
+                {
+                    _reagentSources[id.Prototype].Add(data);
+                }
+
+                usedNames.Add(entProto.Name);
+            }
+
+            if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
+                entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager) &&
+                manager.Solutions.TryGetValue(grindableSolutionId, out var grindableSolution))
+            {
+                var data = new ReagentEntitySourceData(
+                    new() { DefaultGrindCategory },
+                    entProto,
+                    grindableSolution);
+                foreach (var (id, _) in grindableSolution.Contents)
+                {
+                    _reagentSources[id.Prototype].Add(data);
+                }
+                usedNames.Add(entProto.Name);
+            }
+        }
+    }
+
+    public List<ReagentSourceData> GetReagentSources(string id)
+    {
+        return _reagentSources.GetValueOrDefault(id) ?? new List<ReagentSourceData>();
+    }
 }
+
+/// <summary>
+/// A generic class meant to hold information about a reagent source.
+/// </summary>
+public abstract class ReagentSourceData
+{
+    /// <summary>
+    /// The mixing type that applies to this source.
+    /// </summary>
+    public readonly IReadOnlyList<ProtoId<MixingCategoryPrototype>> MixingType;
+
+    /// <summary>
+    /// The number of distinct outputs. Used for primary ordering.
+    /// </summary>
+    public abstract int OutputCount { get; }
+
+    /// <summary>
+    /// A text string corresponding to this source. Typically a name. Used for secondary ordering.
+    /// </summary>
+    public abstract string IdentifierString { get; }
+
+    protected ReagentSourceData(List<ProtoId<MixingCategoryPrototype>> mixingType)
+    {
+        MixingType = mixingType;
+    }
+}
+
+/// <summary>
+/// Used to store a reagent source that's an entity with a corresponding solution.
+/// </summary>
+public sealed class ReagentEntitySourceData : ReagentSourceData
+{
+    public readonly EntityPrototype SourceEntProto;
+
+    public readonly Solution Solution;
+
+    public override int OutputCount => Solution.Contents.Count;
+
+    public override string IdentifierString => SourceEntProto.Name;
+
+    public ReagentEntitySourceData(List<ProtoId<MixingCategoryPrototype>> mixingType, EntityPrototype sourceEntProto, Solution solution)
+        : base(mixingType)
+    {
+        SourceEntProto = sourceEntProto;
+        Solution = solution;
+    }
+}
+
+/// <summary>
+/// Used to store a reagent source that comes from a reaction between multiple reagents.
+/// </summary>
+public sealed class ReagentReactionSourceData : ReagentSourceData
+{
+    public readonly ReactionPrototype ReactionPrototype;
+
+    public override int OutputCount => ReactionPrototype.Products.Count + ReactionPrototype.Reactants.Count(r => r.Value.Catalyst);
+
+    public override string IdentifierString => ReactionPrototype.ID;
+
+    public ReagentReactionSourceData(List<ProtoId<MixingCategoryPrototype>> mixingType, ReactionPrototype reactionPrototype)
+        : base(mixingType)
+    {
+        ReactionPrototype = reactionPrototype;
+    }
+}
+
+/// <summary>
+/// Used to store a reagent source that comes from gas condensation.
+/// </summary>
+public sealed class ReagentGasSourceData : ReagentSourceData
+{
+    public readonly GasPrototype GasPrototype;
+
+    public override int OutputCount => 1;
+
+    public override string IdentifierString => Loc.GetString(GasPrototype.Name);
+
+    public ReagentGasSourceData(List<ProtoId<MixingCategoryPrototype>> mixingType, GasPrototype gasPrototype)
+        : base(mixingType)
+    {
+        GasPrototype = gasPrototype;
+    }
+}
+
index 7b1beeeb6504224cd78cf78279a28bef72ad0eb3..752c9cc6c2c4a922282ba2006aa03c5513941200 100644 (file)
                         <GridContainer Name="RecipesDescriptionContainer"
                                       Margin="10 0 10 0"
                                       Columns="1"
-                                      HSeparationOverride="5"
+                                      HSeparationOverride="0"
                                       HorizontalAlignment="Stretch"
                                       HorizontalExpand="True"/>
                     </CollapsibleBody>
                 </Collapsible>
             </BoxContainer>
+            <BoxContainer Name="SourcesContainer" HorizontalExpand="True">
+                <Collapsible Orientation="Vertical" HorizontalExpand="True">
+                    <CollapsibleHeading Title="{Loc 'guidebook-reagent-sources-header'}"/>
+                    <CollapsibleBody>
+                        <GridContainer Name="SourcesDescriptionContainer"
+                                       Margin="10 0 10 0"
+                                       Columns="1"
+                                       HSeparationOverride="5"
+                                       HorizontalAlignment="Stretch"
+                                       HorizontalExpand="True"/>
+                    </CollapsibleBody>
+                </Collapsible>
+            </BoxContainer>
             <BoxContainer Name="EffectsContainer" HorizontalExpand="True">
                 <Collapsible Orientation="Vertical">
                     <CollapsibleHeading Title="{Loc 'guidebook-reagent-effects-header'}"/>
index cf5a1b6e59cf7536e2a623d7a7dc467201967a0a..e2b09386dfbba1f7cf9b8f22d742d3304086dc81 100644 (file)
@@ -6,10 +6,8 @@ using Content.Client.Message;
 using Content.Client.UserInterface.ControlExtensions;
 using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Localizations;
 using JetBrains.Annotations;
 using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
@@ -99,7 +97,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
 
         #region Recipe
         var reactions = _prototype.EnumeratePrototypes<ReactionPrototype>()
-            .Where(p => p.Products.ContainsKey(reagent.ID))
+            .Where(p => !p.Source && p.Products.ContainsKey(reagent.ID))
             .OrderBy(p => p.Priority)
             .ThenBy(p => p.Products.Count)
             .ToList();
@@ -108,8 +106,7 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
         {
             foreach (var reactionPrototype in reactions)
             {
-                var ctrl = GetRecipeGuide(reactionPrototype);
-                RecipesDescriptionContainer.AddChild(ctrl);
+                RecipesDescriptionContainer.AddChild(new GuideReagentReaction(reactionPrototype, _prototype, _systemManager));
             }
         }
         else
@@ -159,6 +156,8 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
         }
         #endregion
 
+        GenerateSources(reagent);
+
         FormattedMessage description = new();
         description.AddText(reagent.LocalizedDescription);
         description.PushNewline();
@@ -167,64 +166,45 @@ public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag, ISea
         ReagentDescription.SetMessage(description);
     }
 
-    private GuideReagentReaction GetRecipeGuide(ReactionPrototype reactionPrototype)
+    private void GenerateSources(ReagentPrototype reagent)
     {
-        var control = new GuideReagentReaction();
-
-        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<ReagentPrototype>(product).LocalizedName), ("ratio", reactant.Amount)));
-            i++;
-            if (i < reactantsCount)
-                reactantMsg.PushNewline();
-        }
-        reactantMsg.Pop();
-        control.ReactantsLabel.SetMessage(reactantMsg);
-
-        var productMsg = new FormattedMessage();
-        var productCount = reactionPrototype.Products.Count;
-        var u = 0;
-        foreach (var (product, ratio) in reactionPrototype.Products)
+        var sources = _chemistryGuideData.GetReagentSources(reagent.ID);
+        if (sources.Count == 0)
         {
-            productMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
-                ("reagent", _prototype.Index<ReagentPrototype>(product).LocalizedName), ("ratio", ratio)));
-            u++;
-            if (u < productCount)
-                productMsg.PushNewline();
+            SourcesContainer.Visible = false;
+            return;
         }
-        productMsg.Pop();
-        control.ProductsLabel.SetMessage(productMsg);
+        SourcesContainer.Visible = true;
 
-        var mixingCategories = new List<MixingCategoryPrototype>();
-        if (reactionPrototype.MixingCategories != null)
+        var orderedSources = sources
+            .OrderBy(o => o.OutputCount)
+            .ThenBy(o => o.IdentifierString);
+        foreach (var source in orderedSources)
         {
-            foreach (var category in reactionPrototype.MixingCategories)
+            if (source is ReagentEntitySourceData entitySourceData)
             {
-                mixingCategories.Add(_prototype.Index(category));
+                SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+                    entitySourceData.SourceEntProto,
+                    entitySourceData.Solution,
+                    entitySourceData.MixingType,
+                    _prototype,
+                    _systemManager));
+            }
+            else if (source is ReagentReactionSourceData reactionSourceData)
+            {
+                SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+                    reactionSourceData.ReactionPrototype,
+                    _prototype,
+                    _systemManager));
+            }
+            else if (source is ReagentGasSourceData gasSourceData)
+            {
+                SourcesDescriptionContainer.AddChild(new GuideReagentReaction(
+                    gasSourceData.GasPrototype,
+                    gasSourceData.MixingType,
+                    _prototype,
+                    _systemManager));
             }
         }
-
-        // only use the first one for the icon.
-        if (mixingCategories.FirstOrDefault() is { } primaryCategory)
-        {
-            control.MixTexture.Texture = _systemManager.GetEntitySystem<SpriteSystem>().Frame0(primaryCategory.Icon);
-        }
-
-        var mixingVerb = mixingCategories.Count == 0
-            ? Loc.GetString("guidebook-reagent-recipes-mix")
-            : ContentLocalizationManager.FormatList(mixingCategories.Select(p => Loc.GetString(p.VerbText)).ToList());
-
-        var text = Loc.GetString("guidebook-reagent-recipes-mix-info",
-            ("verb", mixingVerb),
-            ("minTemp", reactionPrototype.MinimumTemperature),
-            ("maxTemp", reactionPrototype.MaximumTemperature),
-            ("hasMax", !float.IsPositiveInfinity(reactionPrototype.MaximumTemperature)));
-
-        control.MixLabel.SetMarkup(text);
-        return control;
     }
 }
index 69c14a59af7a056bf76a3f8517493aa23afa7940..becffbdc6d5d61109a79846389558e0f5db165fa 100644 (file)
@@ -1,12 +1,14 @@
 <BoxContainer xmlns="https://spacestation14.io"
               Orientation="Horizontal"
               HorizontalAlignment="Stretch"
-              HorizontalExpand="True">
-    <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
+              HorizontalExpand="True"
+              Margin="0 0 0 5">
+    <BoxContainer Name="ReactantsContainer" Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
         <RichTextLabel Name="ReactantsLabel"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
-                       Access="Public"/>
+                       Access="Public"
+                       Visible="False"/>
     </BoxContainer>
     <BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
         <TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"
@@ -22,6 +24,7 @@
         <RichTextLabel Name="ProductsLabel"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
-                       Access="Public"/>
+                       Access="Public"
+                       Visible="False"/>
     </BoxContainer>
 </BoxContainer>
index fbc6bf13fc4512faedb1e5d26b63bd3b315362ce..168f352d1ab51c67827905ae4e439c1dbb9d1a84 100644 (file)
+using System.Linq;
+using Content.Client.Message;
 using Content.Client.UserInterface.ControlExtensions;
+using Content.Shared.Atmos.Prototypes;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
 using JetBrains.Annotations;
 using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
 using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Graphics.RSI;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
 
 namespace Content.Client.Guidebook.Controls;
 
 [UsedImplicitly, GenerateTypedNameReferences]
 public sealed partial class GuideReagentReaction : BoxContainer, ISearchableControl
 {
+    [ValidatePrototypeId<MixingCategoryPrototype>]
+    private const string DefaultMixingCategory = "DummyMix";
+
+    private readonly IPrototypeManager _protoMan;
+
+    public GuideReagentReaction(IPrototypeManager protoMan)
+    {
+        RobustXamlLoader.Load(this);
+        _protoMan = protoMan;
+    }
+
+    public GuideReagentReaction(ReactionPrototype prototype, IPrototypeManager protoMan, IEntitySystemManager sysMan) : this(protoMan)
+    {
+        var reactantsLabel = ReactantsLabel;
+        SetReagents(prototype.Reactants, ref reactantsLabel, protoMan);
+        var productLabel = ProductsLabel;
+        var products = new Dictionary<string, FixedPoint2>(prototype.Products);
+        foreach (var (reagent, reactantProto) in prototype.Reactants)
+        {
+            if (reactantProto.Catalyst)
+                products.Add(reagent, reactantProto.Amount);
+        }
+        SetReagents(products, ref productLabel, protoMan);
+
+        var mixingCategories = new List<MixingCategoryPrototype>();
+        if (prototype.MixingCategories != null)
+        {
+            foreach (var category in prototype.MixingCategories)
+            {
+                mixingCategories.Add(protoMan.Index(category));
+            }
+        }
+        else
+        {
+            mixingCategories.Add(protoMan.Index<MixingCategoryPrototype>(DefaultMixingCategory));
+        }
+        SetMixingCategory(mixingCategories, prototype, sysMan);
+    }
+
+    public GuideReagentReaction(EntityPrototype prototype,
+        Solution solution,
+        IReadOnlyList<ProtoId<MixingCategoryPrototype>> categories,
+        IPrototypeManager protoMan,
+        IEntitySystemManager sysMan) : this(protoMan)
+    {
+        var icon = sysMan.GetEntitySystem<SpriteSystem>().GetPrototypeIcon(prototype).GetFrame(RsiDirection.South, 0);
+        var entContainer = new BoxContainer
+        {
+            Orientation = LayoutOrientation.Horizontal,
+            HorizontalExpand = true,
+            HorizontalAlignment = HAlignment.Center,
+            Children =
+            {
+                new TextureRect
+                {
+                    Texture = icon
+                }
+            }
+        };
+        var nameLabel = new RichTextLabel();
+        nameLabel.SetMarkup(Loc.GetString("guidebook-reagent-sources-ent-wrapper", ("name", prototype.Name)));
+        entContainer.AddChild(nameLabel);
+        ReactantsContainer.AddChild(entContainer);
+
+        var productLabel = ProductsLabel;
+        SetReagents(solution.Contents, ref productLabel, protoMan);
+        SetMixingCategory(categories, null, sysMan);
+    }
+
+    public GuideReagentReaction(GasPrototype prototype,
+        IReadOnlyList<ProtoId<MixingCategoryPrototype>> categories,
+        IPrototypeManager protoMan,
+        IEntitySystemManager sysMan) : this(protoMan)
+    {
+        ReactantsLabel.Visible = true;
+        ReactantsLabel.SetMarkup(Loc.GetString("guidebook-reagent-sources-gas-wrapper",
+            ("name", Loc.GetString(prototype.Name).ToLower())));
+
+        if (prototype.Reagent != null)
+        {
+            var quantity = new Dictionary<string, FixedPoint2>
+            {
+                { prototype.Reagent, FixedPoint2.New(0.21f) }
+            };
+            var productLabel = ProductsLabel;
+            SetReagents(quantity, ref productLabel, protoMan);
+        }
+        SetMixingCategory(categories, null, sysMan);
+    }
+
+    private void SetReagents(List<ReagentQuantity> reagents, ref RichTextLabel label, IPrototypeManager protoMan)
+    {
+        var amounts = new Dictionary<string, FixedPoint2>();
+        foreach (var (reagent, quantity) in reagents)
+        {
+            amounts.Add(reagent.Prototype, quantity);
+        }
+        SetReagents(amounts, ref label, protoMan);
+    }
+
+    private void SetReagents(
+        Dictionary<string, ReactantPrototype> reactants,
+        ref RichTextLabel label,
+        IPrototypeManager protoMan)
+    {
+        var amounts = new Dictionary<string, FixedPoint2>();
+        foreach (var (reagent, reactantPrototype) in reactants)
+        {
+            amounts.Add(reagent, reactantPrototype.Amount);
+        }
+        SetReagents(amounts, ref label, protoMan);
+    }
+
+    [PublicAPI]
+    private void SetReagents(
+        Dictionary<ProtoId<MixingCategoryPrototype>, ReactantPrototype> reactants,
+        ref RichTextLabel label,
+        IPrototypeManager protoMan)
+    {
+        var amounts = new Dictionary<string, FixedPoint2>();
+        foreach (var (reagent, reactantPrototype) in reactants)
+        {
+            amounts.Add(reagent, reactantPrototype.Amount);
+        }
+        SetReagents(amounts, ref label, protoMan);
+    }
+
+    private void SetReagents(Dictionary<string, FixedPoint2> reagents, ref RichTextLabel label, IPrototypeManager protoMan)
+    {
+        var msg = new FormattedMessage();
+        var reagentCount = reagents.Count;
+        var i = 0;
+        foreach (var (product, amount) in reagents.OrderByDescending(p => p.Value))
+        {
+            msg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+                ("reagent", protoMan.Index<ReagentPrototype>(product).LocalizedName), ("ratio", amount)));
+            i++;
+            if (i < reagentCount)
+                msg.PushNewline();
+        }
+        msg.Pop();
+        label.SetMessage(msg);
+        label.Visible = true;
+    }
+
+    private void SetMixingCategory(IReadOnlyList<ProtoId<MixingCategoryPrototype>> mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
+    {
+        var foo = new List<MixingCategoryPrototype>();
+        foreach (var cat in mixingCategories)
+        {
+            foo.Add(_protoMan.Index(cat));
+        }
+        SetMixingCategory(foo, prototype, sysMan);
+    }
+
+    private void SetMixingCategory(IReadOnlyList<MixingCategoryPrototype> mixingCategories, ReactionPrototype? prototype, IEntitySystemManager sysMan)
+    {
+        if (mixingCategories.Count == 0)
+            return;
+
+        // only use the first one for the icon.
+        if (mixingCategories.First() is { } primaryCategory)
+        {
+            MixTexture.Texture = sysMan.GetEntitySystem<SpriteSystem>().Frame0(primaryCategory.Icon);
+        }
+
+        var mixingVerb = ContentLocalizationManager.FormatList(mixingCategories
+            .Select(p => Loc.GetString(p.VerbText)).ToList());
+
+        var minTemp = prototype?.MinimumTemperature ?? 0;
+        var maxTemp = prototype?.MaximumTemperature ?? float.PositiveInfinity;
+        var text = Loc.GetString("guidebook-reagent-recipes-mix-info",
+            ("verb", mixingVerb),
+            ("minTemp", minTemp),
+            ("maxTemp", maxTemp),
+            ("hasMax", !float.IsPositiveInfinity(maxTemp)));
+
+        MixLabel.SetMarkup(text);
+    }
+
     public bool CheckMatchesSearch(string query)
     {
         return this.ChildrenContainText(query);
diff --git a/Content.Server/Kitchen/Components/ExtractableComponent.cs b/Content.Server/Kitchen/Components/ExtractableComponent.cs
deleted file mode 100644 (file)
index a1949b8..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using Content.Server.Kitchen.EntitySystems;
-using Content.Shared.Chemistry.Components;
-
-namespace Content.Server.Kitchen.Components
-{
-    /// <summary>
-    /// Tag component that denotes an entity as Extractable
-    /// </summary>
-    [RegisterComponent]
-    [Access(typeof(ReagentGrinderSystem))]
-    public sealed partial class ExtractableComponent : Component
-    {
-        [DataField("juiceSolution")]
-        public Solution? JuiceSolution;
-
-        [DataField("grindableSolutionName")]
-        public string? GrindableSolution;
-    }
-}
index cd9a82a0727cfea645a50059ae90f431f9a8ac18..e4766b4ab953f124f65b7cdbdc457e3bbbdb3365 100644 (file)
@@ -9,6 +9,7 @@ using Content.Shared.Containers.ItemSlots;
 using Content.Shared.FixedPoint;
 using Content.Shared.Interaction;
 using Content.Shared.Kitchen;
+using Content.Shared.Kitchen.Components;
 using Content.Shared.Popups;
 using Content.Shared.Random;
 using Content.Shared.Stacks;
index 7541784905ef22b7d55a27ccebadf54d934675af..f4e9619b172f100ca9b17ec337df4f17aa2d1fed 100644 (file)
@@ -83,6 +83,17 @@ namespace Content.Shared.Chemistry.Reaction
         [DataField("priority")]
         public int Priority;
 
+        /// <summary>
+        /// Determines whether or not this reaction creates a new chemical (false) or if it's a breakdown for existing chemicals (true)
+        /// Used in the chemistry guidebook to make divisions between recipes and reaction sources.
+        /// </summary>
+        /// <example>
+        /// Mixing together two reagents to get a third -> false
+        /// Heating a reagent to break it down into 2 different ones -> true
+        /// </example>
+        [DataField]
+        public bool Source;
+
         /// <summary>
         ///     Comparison for creating a sorted set of reactions. Determines the order in which reactions occur.
         /// </summary>
diff --git a/Content.Shared/Kitchen/Components/ExtractableComponent.cs b/Content.Shared/Kitchen/Components/ExtractableComponent.cs
new file mode 100644 (file)
index 0000000..bff2166
--- /dev/null
@@ -0,0 +1,17 @@
+using Content.Shared.Chemistry.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Kitchen.Components;
+
+/// <summary>
+/// Tag component that denotes an entity as Extractable
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ExtractableComponent : Component
+{
+    [DataField("juiceSolution")]
+    public Solution? JuiceSolution;
+
+    [DataField("grindableSolutionName")]
+    public string? GrindableSolution;
+};
index 1ad9958804b73726bb46e3a7820f0bc3338f5e59..a486ed8ede3dbdabd8c1bb28c4b155e38eeb36a3 100644 (file)
@@ -1,4 +1,8 @@
 # Types
+mixing-verb-default-mix = mix
+mixing-verb-default-grind = grind
+mixing-verb-default-juice = juice
+mixing-verb-default-condense = condense
 mixing-verb-centrifuge = centrifugation
 mixing-verb-electrolysis = electrolyze
 mixing-verb-holy = bless
index 7b39a6317573585ec80986206b084428afa18d69..8bb78681843e7c52dabf53f148fcd1cf060c167a 100644 (file)
 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-sources-header = Sources
+guidebook-reagent-sources-ent-wrapper = [bold]{$name}[/bold] \[1\]
+guidebook-reagent-sources-gas-wrapper = [bold]{$name} (gas)[/bold] \[1\]
 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 = [italic]Seems to be {$description}.[/italic]
 guidebook-reagent-recipes-mix-info = {$minTemp ->
     [0] {$hasMax ->
-            [true] {$verb} below {$maxTemp}K
-            *[false] {$verb}
+            [true] {CAPITALIZE($verb)} below {$maxTemp}K
+            *[false] {CAPITALIZE($verb)}
         }
-    *[other] {$verb} {$hasMax ->
+    *[other] {CAPITALIZE($verb)} {$hasMax ->
             [true] between {$minTemp}K and {$maxTemp}K
             *[false] above {$minTemp}K
         }
index dbf14c19c1fbde4df34c4453de495331b0512d8e..20d58e70abd9ac23d75f4b42f7176e86327be7b7 100644 (file)
@@ -1,3 +1,36 @@
+# Default Mixing
+# Not actually used in reactions: only meant for guidebook display purposes.
+
+- type: mixingCategory
+  id: DummyMix
+  verbText: mixing-verb-default-mix
+  icon:
+    sprite: Objects/Specific/Chemistry/beaker_large.rsi
+    state: beakerlarge
+
+- type: mixingCategory
+  id: DummyGrind
+  verbText: mixing-verb-default-grind
+  icon:
+    sprite: Structures/Machines/juicer.rsi
+    state: juicer0
+
+- type: mixingCategory
+  id: DummyJuice
+  verbText: mixing-verb-default-juice
+  icon:
+    sprite: Structures/Machines/juicer.rsi
+    state: juicer0
+
+- type: mixingCategory
+  id: DummyCondense
+  verbText: mixing-verb-default-condense
+  icon:
+    sprite: Structures/Piping/Atmospherics/condenser.rsi
+    state: display
+
+# Alternative Mixing Methods
+
 - type: mixingCategory
   id: Centrifuge
   verbText: mixing-verb-centrifuge
index 15159ba69de4fe73bd05cf90959aaabe47f296b1..76c1f2046044986e20f72e766e62f707e00f2266 100644 (file)
@@ -26,7 +26,7 @@
 - type: entity
   parent: PartBase
   id: PartRodMetal
-  name: metal rods
+  name: metal rod
   suffix: Full
   components:
   - type: PhysicalComposition
index b126a23abe412b2677529add6b18bfc9b8d33923..4e94b8d85bc5b390fff45962ddb2bf854970c124 100644 (file)
@@ -1,5 +1,6 @@
 - type: reaction
   id: BloodBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
@@ -14,6 +15,7 @@
 
 - type: reaction
   id: SlimeBloodBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
@@ -25,6 +27,7 @@
 
 - type: reaction
   id: CopperBloodBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
@@ -39,6 +42,7 @@
 
 - type: reaction
   id: ZombieBloodBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
index b2fb6a87521d55c79114141bb609db842a8164bc..df21e7bac303547e834af335e9bbeb18aecb90e7 100644 (file)
@@ -10,6 +10,7 @@
 
 - type: reaction
   id: CelluloseBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
 
 - type: reaction
   id: TableSaltBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
index 73cc10cf8535d8bfdf6d53604417c82ee83676b7..d278ef24c41460ff2c01ae69a2578ccb8e58a56c 100644 (file)
 
 - type: reaction
   id: EthanolBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
 
 - type: reaction
   id: WaterBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
index 39bf0d20014383f21d5e6bd2275f0b15c41f26f5..fbbf65c5358b3ba5887a2a421f804f0447f92ab0 100644 (file)
 
 - type: reaction
   id: BananaBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
 
 #- type: reaction
 #  id: SugarBreakdown
-# minTemp: 520
- # reactants:
- #   Sugar:
- #     amount: 4
- # products:
- #   Carbon: 1
- #   Oxygen: 1
- #   Hydrogen: 2
+#  source: true
+#  minTemp: 520
+#  reactants:
+#    Sugar:
+#      amount: 4
+#  products:
+#    Carbon: 1
+#    Oxygen: 1
+#    Hydrogen: 2
 
index 35ec50059091605c0878b3ad200e6156b2920903..818030a3b33d7f9983bb37414c2cee807ea7387c 100644 (file)
 
 - type: reaction
   id: FiberBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
index 9cc6db1b64e1ce4f195800f0722c1e26bd26ec3b..3dc6654d6816194cbd3a5fbb63503e241165a19a 100644 (file)
@@ -1,5 +1,6 @@
 - type: reaction
   id: CarbonDioxideBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
@@ -11,6 +12,7 @@
 
 - type: reaction
   id: NitrousOxideBreakdown
+  source: true
   requiredMixerCategories:
   - Electrolysis
   reactants:
index b0f71b4c688e4d8ae72d051e8b842679e9ae8098..6b1aff4be6789eb68dfc853a0984683f987d2f45 100644 (file)
@@ -63,6 +63,7 @@
 
 - type: reaction
   id: WeldingFuelBreakdown
+  source: true
   requiredMixerCategories:
   - Centrifuge
   reactants:
diff --git a/Resources/Textures/Structures/Piping/Atmospherics/condenser.rsi/display.png b/Resources/Textures/Structures/Piping/Atmospherics/condenser.rsi/display.png
new file mode 100644 (file)
index 0000000..4dc1017
Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/condenser.rsi/display.png differ
index 591c223e5cd461fbaa604964b05dba7fc8296ea1..05a1e617a51b36a5fb5e2edb6e2ece225c8084ea 100644 (file)
@@ -35,6 +35,9 @@
             "name":"pipe",
             "directions":4
         },
+        {
+            "name":"display"
+        },
         {
             "name":"fill-1"
         },