]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add support for printing reagents in lathes (#30476)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Thu, 1 Aug 2024 04:15:05 +0000 (00:15 -0400)
committerGitHub <noreply@github.com>
Thu, 1 Aug 2024 04:15:05 +0000 (14:15 +1000)
* Add support for reagents in lathes

* missing locale

17 files changed:
Content.Client/Lathe/UI/LatheMenu.xaml
Content.Client/Lathe/UI/LatheMenu.xaml.cs
Content.Client/Lathe/UI/RecipeControl.xaml
Content.Client/Lathe/UI/RecipeControl.xaml.cs
Content.IntegrationTests/Tests/MaterialArbitrageTest.cs
Content.Server/Cargo/Systems/PricingSystem.cs
Content.Server/Lathe/LatheSystem.cs
Content.Server/UserInterface/StatValuesCommand.cs
Content.Shared/Construction/MachinePartSystem.cs
Content.Shared/Lathe/LatheComponent.cs
Content.Shared/Lathe/SharedLatheSystem.cs
Content.Shared/Research/Prototypes/LatheRecipePrototype.cs
Content.Shared/Research/Systems/SharedResearchSystem.cs
Content.Shared/Research/TechnologyDisk/Systems/TechnologyDiskSystem.cs
Resources/Locale/en-US/lathe/recipes.ftl [new file with mode: 0644]
Resources/Locale/en-US/lathe/ui/lathe-menu.ftl
Resources/Prototypes/Recipes/Lathes/medical.yml

index e3247fe7037af77b111fe96ede78d43af96bdac3..5b21f0bae66d0b3174c4aab8fc062c6487e405ed 100644 (file)
                             Margin="5 0 0 0"
                             Text="{Loc 'lathe-menu-fabricating-message'}">
                         </Label>
-                        <EntityPrototypeView
-                            Name="FabricatingEntityProto"
-                            HorizontalAlignment="Left"
-                            Margin="100 0 0 0"
-                             />
+                        <BoxContainer Name="FabricatingDisplayContainer"
+                                      HorizontalAlignment="Left"
+                                      Margin="100 0 0 0"/>
                         <Label
                             Name="NameLabel"
                             RectClipContent="True"
index 6f530b76c75822ba85713aa707ea21e9774f38d1..630ef41e9fb0b6ed13c74fc417990f008bf7a08b 100644 (file)
@@ -1,18 +1,15 @@
-using System.Buffers;
 using System.Linq;
 using System.Text;
 using Content.Client.Materials;
 using Content.Shared.Lathe;
 using Content.Shared.Lathe.Prototypes;
-using Content.Shared.Materials;
 using Content.Shared.Research.Prototypes;
 using Robust.Client.AutoGenerated;
 using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.CustomControls;
 using Robust.Client.UserInterface.XAML;
-using Robust.Client.ResourceManagement;
-using Robust.Client.Graphics;
 using Robust.Shared.Prototypes;
 
 namespace Content.Client.Lathe.UI;
@@ -92,7 +89,7 @@ public sealed partial class LatheMenu : DefaultWindow
 
             if (SearchBar.Text.Trim().Length != 0)
             {
-                if (proto.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
+                if (_lathe.GetRecipeName(recipe).ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant()))
                     recipesToShow.Add(proto);
             }
             else
@@ -104,19 +101,15 @@ public sealed partial class LatheMenu : DefaultWindow
         if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0)
             quantity = 1;
 
-        var sortedRecipesToShow = recipesToShow.OrderBy(p => p.Name);
+        var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
         RecipeList.Children.Clear();
         _entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
 
         foreach (var prototype in sortedRecipesToShow)
         {
-            EntityPrototype? recipeProto = null;
-            if (_prototypeManager.TryIndex(prototype.Result, out EntityPrototype? entityProto))
-                recipeProto = entityProto;
-
             var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe);
 
-            var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, recipeProto);
+            var control = new RecipeControl(_lathe, prototype, () => GenerateTooltipText(prototype), canProduce, GetRecipeDisplayControl(prototype));
             control.OnButtonPressed += s =>
             {
                 if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -132,9 +125,9 @@ public sealed partial class LatheMenu : DefaultWindow
         StringBuilder sb = new();
         var multiplier = _entityManager.GetComponent<LatheComponent>(Entity).MaterialUseMultiplier;
 
-        foreach (var (id, amount) in prototype.RequiredMaterials)
+        foreach (var (id, amount) in prototype.Materials)
         {
-            if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
+            if (!_prototypeManager.TryIndex(id, out var proto))
                 continue;
 
             var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier);
@@ -163,8 +156,9 @@ public sealed partial class LatheMenu : DefaultWindow
             sb.AppendLine(tooltipText);
         }
 
-        if (!string.IsNullOrWhiteSpace(prototype.Description))
-            sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
+        var desc = _lathe.GetRecipeDescription(prototype);
+        if (!string.IsNullOrWhiteSpace(desc))
+            sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", desc)));
 
         // Remove last newline
         if (sb.Length > 0)
@@ -222,13 +216,10 @@ public sealed partial class LatheMenu : DefaultWindow
             var queuedRecipeBox = new BoxContainer();
             queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
 
-            var queuedRecipeProto = new EntityPrototypeView();
-            queuedRecipeBox.AddChild(queuedRecipeProto);
-            if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
-                queuedRecipeProto.SetPrototype(entityProto);
+            queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
 
             var queuedRecipeLabel = new Label();
-            queuedRecipeLabel.Text = $"{idx}. {recipe.Name}";
+            queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
             queuedRecipeBox.AddChild(queuedRecipeLabel);
             QueueList.AddChild(queuedRecipeBox);
             idx++;
@@ -241,10 +232,29 @@ public sealed partial class LatheMenu : DefaultWindow
         if (recipe == null)
             return;
 
-        if (_prototypeManager.TryIndex(recipe.Result, out EntityPrototype? entityProto) && entityProto != null)
-            FabricatingEntityProto.SetPrototype(entityProto);
+        FabricatingDisplayContainer.Children.Clear();
+        FabricatingDisplayContainer.AddChild(GetRecipeDisplayControl(recipe));
+
+        NameLabel.Text = _lathe.GetRecipeName(recipe);
+    }
+
+    public Control GetRecipeDisplayControl(LatheRecipePrototype recipe)
+    {
+        if (recipe.Icon != null)
+        {
+            var textRect = new TextureRect();
+            textRect.Texture = _spriteSystem.Frame0(recipe.Icon);
+            return textRect;
+        }
+
+        if (recipe.Result is { } result)
+        {
+            var entProtoView = new EntityPrototypeView();
+            entProtoView.SetPrototype(result);
+            return entProtoView;
+        }
 
-        NameLabel.Text = $"{recipe.Name}";
+        return new Control();
     }
 
     private void OnItemSelected(OptionButton.ItemSelectedEventArgs obj)
index 19e20c7c06d3e553887294cb5efc587e9725230e..1105ab478ffc5e0a4ccf88550d7004035540f528 100644 (file)
@@ -5,8 +5,8 @@
         Margin="0"
         StyleClasses="ButtonSquare">
         <BoxContainer Orientation="Horizontal">
-            <EntityPrototypeView
-                Name="RecipePrototype"
+            <BoxContainer
+                Name="RecipeDisplayContainer"
                 Margin="0 0 4 0"
                 HorizontalAlignment="Center"
                 VerticalAlignment="Center"
index db428d3cf0e92967d77a676a5d8c59b5155fd390..4f438c8a8e54c9379260c5c1212395a63321747d 100644 (file)
@@ -1,10 +1,7 @@
 using Content.Shared.Research.Prototypes;
 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;
 
 namespace Content.Client.Lathe.UI;
 
@@ -14,13 +11,12 @@ public sealed partial class RecipeControl : Control
     public Action<string>? OnButtonPressed;
     public Func<string> TooltipTextSupplier;
 
-    public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, EntityPrototype? entityPrototype = null)
+    public RecipeControl(LatheSystem latheSystem, LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Control displayControl)
     {
         RobustXamlLoader.Load(this);
 
-        RecipeName.Text = recipe.Name;
-        if (entityPrototype != null)
-            RecipePrototype.SetPrototype(entityPrototype);
+        RecipeName.Text = latheSystem.GetRecipeName(recipe);
+        RecipeDisplayContainer.AddChild(displayControl);
         Button.Disabled = !canProduce;
         TooltipTextSupplier = tooltipTextSupplier;
         Button.TooltipSupplier = SupplyTooltip;
index f64b7c79dfea279d578a8374cfbc6b5dc3d2d7ec..c48afd819bed4a96c9ed4126f252ae8288cfe114 100644 (file)
@@ -193,7 +193,7 @@ public sealed class MaterialArbitrageTest
                 {
                     foreach (var recipe in recipes)
                     {
-                        foreach (var (matId, amount) in recipe.RequiredMaterials)
+                        foreach (var (matId, amount) in recipe.Materials)
                         {
                             var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
                             if (spawnedMats.TryGetValue(matId, out var numSpawned))
@@ -273,7 +273,7 @@ public sealed class MaterialArbitrageTest
                 {
                     foreach (var recipe in recipes)
                     {
-                        foreach (var (matId, amount) in recipe.RequiredMaterials)
+                        foreach (var (matId, amount) in recipe.Materials)
                         {
                             var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
                             if (deconstructedMats.TryGetValue(matId, out var numSpawned))
@@ -328,7 +328,7 @@ public sealed class MaterialArbitrageTest
                 {
                     foreach (var recipe in recipes)
                     {
-                        foreach (var (matId, amount) in recipe.RequiredMaterials)
+                        foreach (var (matId, amount) in recipe.Materials)
                         {
                             var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
                             if (compositionComponent.MaterialComposition.TryGetValue(matId, out var numSpawned))
index f878eeee75cc830b1060f8a343ddd0baeaabea09..d936451d524d3d5a45bfe00764dd5b717197075a 100644 (file)
@@ -16,6 +16,7 @@ using Robust.Shared.Map.Components;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 using System.Linq;
+using Content.Shared.Research.Prototypes;
 
 namespace Content.Server.Cargo.Systems;
 
@@ -158,6 +159,26 @@ public sealed class PricingSystem : EntitySystem
         return price;
     }
 
+    public double GetLatheRecipePrice(LatheRecipePrototype recipe)
+    {
+        var price = 0.0;
+
+        if (recipe.Result is { } result)
+        {
+            price += GetEstimatedPrice(_prototypeManager.Index(result));
+        }
+
+        if (recipe.ResultReagents is { } resultReagents)
+        {
+            foreach (var (reagent, amount) in resultReagents)
+            {
+                price += (_prototypeManager.Index(reagent).PricePerUnit * amount).Double();
+            }
+        }
+
+        return price;
+    }
+
     /// <summary>
     /// Get a rough price for an entityprototype. Does not consider contained entities.
     /// </summary>
index 7448a9b84dd25154359a5cbe1db321e01389b9bb..f6e5903bbe2b59c68dc40cb6f83fad676b6a917a 100644 (file)
@@ -1,23 +1,29 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Content.Server.Administration.Logs;
-using Content.Server.Atmos;
 using Content.Server.Atmos.EntitySystems;
+using Content.Server.Fluids.EntitySystems;
 using Content.Server.Lathe.Components;
 using Content.Server.Materials;
+using Content.Server.Popups;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Server.Stack;
 using Content.Shared.Atmos;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Chemistry.Reagent;
 using Content.Shared.UserInterface;
 using Content.Shared.Database;
 using Content.Shared.Emag.Components;
+using Content.Shared.Examine;
 using Content.Shared.Lathe;
 using Content.Shared.Materials;
 using Content.Shared.ReagentSpeed;
 using Content.Shared.Research.Components;
 using Content.Shared.Research.Prototypes;
 using JetBrains.Annotations;
+using Robust.Server.Containers;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Prototypes;
@@ -34,9 +40,13 @@ namespace Content.Server.Lathe
         [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
+        [Dependency] private readonly ContainerSystem _container = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSys = default!;
         [Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
+        [Dependency] private readonly PopupSystem _popup = default!;
+        [Dependency] private readonly PuddleSystem _puddle = default!;
         [Dependency] private readonly ReagentSpeedSystem _reagentSpeed = default!;
+        [Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
         [Dependency] private readonly StackSystem _stack = default!;
         [Dependency] private readonly TransformSystem _transform = default!;
 
@@ -63,7 +73,6 @@ namespace Content.Server.Lathe
             SubscribeLocalEvent<EmagLatheRecipesComponent, LatheGetRecipesEvent>(GetEmagLatheRecipes);
             SubscribeLocalEvent<LatheHeatProducingComponent, LatheStartPrintingEvent>(OnHeatStartPrinting);
         }
-
         public override void Update(float frameTime)
         {
             var query = EntityQueryEnumerator<LatheProducingComponent, LatheComponent>();
@@ -119,7 +128,7 @@ namespace Content.Server.Lathe
             {
                 if (!_proto.TryIndex(id, out var proto))
                     continue;
-                foreach (var (mat, _) in proto.RequiredMaterials)
+                foreach (var (mat, _) in proto.Materials)
                 {
                     if (!materialWhitelist.Contains(mat))
                     {
@@ -165,7 +174,7 @@ namespace Content.Server.Lathe
             if (!CanProduce(uid, recipe, 1, component))
                 return false;
 
-            foreach (var (mat, amount) in recipe.RequiredMaterials)
+            foreach (var (mat, amount) in recipe.Materials)
             {
                 var adjustedAmount = recipe.ApplyMaterialDiscount
                     ? (int) (-amount * component.MaterialUseMultiplier)
@@ -211,8 +220,31 @@ namespace Content.Server.Lathe
 
             if (comp.CurrentRecipe != null)
             {
-                var result = Spawn(comp.CurrentRecipe.Result, Transform(uid).Coordinates);
-                _stack.TryMergeToContacts(result);
+                if (comp.CurrentRecipe.Result is { } resultProto)
+                {
+                    var result = Spawn(resultProto, Transform(uid).Coordinates);
+                    _stack.TryMergeToContacts(result);
+                }
+
+                if (comp.CurrentRecipe.ResultReagents is { } resultReagents &&
+                    comp.ReagentOutputSlotId is { } slotId)
+                {
+                    var toAdd = new Solution(
+                        resultReagents.Select(p => new ReagentQuantity(p.Key.Id, p.Value, null)));
+
+                    // dispense it in the container if we have it and dump it if we don't
+                    if (_container.TryGetContainer(uid, slotId, out var container) &&
+                        container.ContainedEntities.Count == 1 &&
+                        _solution.TryGetFitsInDispenser(container.ContainedEntities.First(), out var solution, out _))
+                    {
+                        _solution.AddSolution(solution.Value, toAdd);
+                    }
+                    else
+                    {
+                        _popup.PopupEntity(Loc.GetString("lathe-reagent-dispense-no-container", ("name", uid)), uid);
+                        _puddle.TrySpillAt(uid, toAdd, out _);
+                    }
+                }
             }
 
             comp.CurrentRecipe = null;
@@ -327,6 +359,7 @@ namespace Content.Server.Lathe
         {
             return GetAvailableRecipes(uid, component).Contains(recipe.ID);
         }
+
         #region UI Messages
 
         private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, LatheQueueRecipeMessage args)
@@ -343,8 +376,9 @@ namespace Content.Server.Lathe
                 }
                 if (count > 0)
                 {
-                    _adminLogger.Add(LogType.Action, LogImpact.Low,
-                        $"{ToPrettyString(args.Actor):player} queued {count} {recipe.Name} at {ToPrettyString(uid):lathe}");
+                    _adminLogger.Add(LogType.Action,
+                        LogImpact.Low,
+                        $"{ToPrettyString(args.Actor):player} queued {count} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
                 }
             }
             TryStartProducing(uid, component);
index cb599f7b09f75b4f78508f338d7d93aa84288426..85b979f83a7487c71fc24bba9aa30a054d325b9e 100644 (file)
@@ -7,7 +7,6 @@ using Content.Server.Item;
 using Content.Server.Power.Components;
 using Content.Shared.Administration;
 using Content.Shared.Item;
-using Content.Shared.Materials;
 using Content.Shared.Research.Prototypes;
 using Content.Shared.UserInterface;
 using Content.Shared.Weapons.Melee;
@@ -224,13 +223,13 @@ public sealed class StatValuesCommand : IConsoleCommand
         {
             var cost = 0.0;
 
-            foreach (var (material, count) in proto.RequiredMaterials)
+            foreach (var (material, count) in proto.Materials)
             {
-                var materialPrice = _proto.Index<MaterialPrototype>(material).Price;
+                var materialPrice = _proto.Index(material).Price;
                 cost += materialPrice * count;
             }
 
-            var sell = priceSystem.GetEstimatedPrice(_proto.Index<EntityPrototype>(proto.Result));
+            var sell = priceSystem.GetLatheRecipePrice(proto);
 
             values.Add(new[]
             {
index f357a395e64ffc384b24eebb53822aa1bc8ee30b..f3314dfc11bc21cbe6cb8fe49b4f26b9c57b36f0 100644 (file)
@@ -81,9 +81,9 @@ namespace Content.Shared.Construction
                 {
                     var partRecipe = recipes[0];
                     if (recipes.Count > 1)
-                        partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum());
+                        partRecipe = recipes.MinBy(p => p.Materials.Values.Sum());
 
-                    foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials)
+                    foreach (var (mat, matAmount) in partRecipe!.Materials)
                     {
                         materials.TryAdd(mat, 0);
                         materials[mat] += matAmount * amount * coefficient;
@@ -101,9 +101,9 @@ namespace Content.Shared.Construction
                 {
                     var partRecipe = recipes[0];
                     if (recipes.Count > 1)
-                        partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum());
+                        partRecipe = recipes.MinBy(p => p.Materials.Values.Sum());
 
-                    foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials)
+                    foreach (var (mat, matAmount) in partRecipe!.Materials)
                     {
                         materials.TryAdd(mat, 0);
                         materials[mat] += matAmount * amount * coefficient;
index 5039e78cb0761e7174ec0c9ecddb6d33b0b8b24e..d776a65e2330fe93a26059c87a00a122b13f6a79 100644 (file)
@@ -33,6 +33,9 @@ namespace Content.Shared.Lathe
         [DataField]
         public SoundSpecifier? ProducingSound;
 
+        [DataField]
+        public string? ReagentOutputSlotId;
+
         #region Visualizer info
         [DataField]
         public string? IdleState;
index b41a91f9c3476f92fea4f6a3e5554d094421b4e9..e240571f3153db410b1bdb9cad2a7d772ec95332 100644 (file)
@@ -1,5 +1,8 @@
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using Content.Shared.Emag.Systems;
+using Content.Shared.Examine;
+using Content.Shared.Localizations;
 using Content.Shared.Materials;
 using Content.Shared.Research.Prototypes;
 using JetBrains.Annotations;
@@ -23,10 +26,21 @@ public abstract class SharedLatheSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<EmagLatheRecipesComponent, GotEmaggedEvent>(OnEmagged);
+        SubscribeLocalEvent<LatheComponent, ExaminedEvent>(OnExamined);
         SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
+
         BuildInverseRecipeDictionary();
     }
 
+    private void OnExamined(Entity<LatheComponent> ent, ref ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        if (ent.Comp.ReagentOutputSlotId != null)
+            args.PushMarkup(Loc.GetString("lathe-menu-reagent-slot-examine"));
+    }
+
     [PublicAPI]
     public bool CanProduce(EntityUid uid, string recipe, int amount = 1, LatheComponent? component = null)
     {
@@ -40,7 +54,7 @@ public abstract class SharedLatheSystem : EntitySystem
         if (!HasRecipe(uid, recipe, component))
             return false;
 
-        foreach (var (material, needed) in recipe.RequiredMaterials)
+        foreach (var (material, needed) in recipe.Materials)
         {
             var adjustedAmount = AdjustMaterial(needed, recipe.ApplyMaterialDiscount, component.MaterialUseMultiplier);
 
@@ -72,6 +86,9 @@ public abstract class SharedLatheSystem : EntitySystem
         _inverseRecipeDictionary.Clear();
         foreach (var latheRecipe in _proto.EnumeratePrototypes<LatheRecipePrototype>())
         {
+            if (latheRecipe.Result == null)
+                continue;
+
             _inverseRecipeDictionary.GetOrNew(latheRecipe.Result).Add(latheRecipe);
         }
     }
@@ -83,4 +100,55 @@ public abstract class SharedLatheSystem : EntitySystem
             recipes.AddRange(r);
         return recipes.Count != 0;
     }
+
+    public string GetRecipeName(ProtoId<LatheRecipePrototype> proto)
+    {
+        return GetRecipeName(_proto.Index(proto));
+    }
+
+    public string GetRecipeName(LatheRecipePrototype proto)
+    {
+        if (!string.IsNullOrWhiteSpace(proto.Name))
+            return Loc.GetString(proto.Name);
+
+        if (proto.Result is { } result)
+        {
+            return _proto.Index(result).Name;
+        }
+
+        if (proto.ResultReagents is { } resultReagents)
+        {
+            return ContentLocalizationManager.FormatList(resultReagents
+                .Select(p => Loc.GetString("lathe-menu-result-reagent-display", ("reagent", _proto.Index(p.Key).LocalizedName), ("amount", p.Value)))
+                .ToList());
+        }
+
+        return string.Empty;
+    }
+
+    [PublicAPI]
+    public string GetRecipeDescription(ProtoId<LatheRecipePrototype> proto)
+    {
+        return GetRecipeDescription(_proto.Index(proto));
+    }
+
+    public string GetRecipeDescription(LatheRecipePrototype proto)
+    {
+        if (!string.IsNullOrWhiteSpace(proto.Description))
+            return Loc.GetString(proto.Description);
+
+        if (proto.Result is { } result)
+        {
+            return _proto.Index(result).Description;
+        }
+
+        if (proto.ResultReagents is { } resultReagents)
+        {
+            // We only use the first one for the description since these descriptions don't combine very well.
+            var reagent = resultReagents.First().Key;
+            return _proto.Index(reagent).LocalizedDescription;
+        }
+
+        return string.Empty;
+    }
 }
index 8b0c866cbe3b7443a56b138ae842053e49b47d94..40c20df343738cea8e3fb89d5998a742b6fe8f2b 100644 (file)
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
 using Content.Shared.Lathe.Prototypes;
 using Content.Shared.Materials;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Research.Prototypes
 {
-    [NetSerializable, Serializable, Prototype("latheRecipe")]
+    [NetSerializable, Serializable, Prototype]
     public sealed partial class LatheRecipePrototype : IPrototype
     {
         [ViewVariables]
         [IdDataField]
         public string ID { get; private set; } = default!;
 
-        [DataField("name")]
-        private string _name = string.Empty;
+        /// <summary>
+        ///     Name displayed in the lathe GUI.
+        /// </summary>
+        [DataField]
+        public LocId? Name;
 
-        [DataField("description")]
-        private string _description = string.Empty;
+        /// <summary>
+        ///     Short description displayed in the lathe GUI.
+        /// </summary>
+        [DataField]
+        public LocId? Description;
 
         /// <summary>
         ///     The prototype name of the resulting entity when the recipe is printed.
         /// </summary>
-        [DataField("result", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
-        public string Result = string.Empty;
+        [DataField]
+        public EntProtoId? Result;
+
+        [DataField]
+        public Dictionary<ProtoId<ReagentPrototype>, FixedPoint2>? ResultReagents;
 
         /// <summary>
         ///     An entity whose sprite is displayed in the ui in place of the actual recipe result.
         /// </summary>
-        [DataField("icon")]
+        [DataField]
         public SpriteSpecifier? Icon;
 
         [DataField("completetime")]
-        private TimeSpan _completeTime = TimeSpan.FromSeconds(5);
-
-        [DataField("materials", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MaterialPrototype>))]
-        private Dictionary<string, int> _requiredMaterials = new();
-
-        //todo make these function calls because we're eating tons of resolves here.
-        /// <summary>
-        ///     Name displayed in the lathe GUI.
-        /// </summary>
-        [ViewVariables]
-        public string Name
-        {
-            get
-            {
-                if (_name.Trim().Length != 0)
-                    return _name;
-                var protoMan = IoCManager.Resolve<IPrototypeManager>();
-                protoMan.TryIndex(Result, out EntityPrototype? prototype);
-                if (prototype?.Name != null)
-                    _name = prototype.Name;
-                return _name;
-            }
-        }
-
-        /// <summary>
-        ///     Short description displayed in the lathe GUI.
-        /// </summary>
-        [ViewVariables]
-        public string Description
-        {
-            get
-            {
-                if (_description.Trim().Length != 0)
-                    return _description;
-                var protoMan = IoCManager.Resolve<IPrototypeManager>();
-                protoMan.TryIndex(Result, out EntityPrototype? prototype);
-                if (prototype?.Description != null)
-                    _description = prototype.Description;
-                return _description;
-            }
-        }
+        public TimeSpan CompleteTime = TimeSpan.FromSeconds(5);
 
         /// <summary>
         ///     The materials required to produce this recipe.
         ///     Takes a material ID as string.
         /// </summary>
-        [ViewVariables]
-        public Dictionary<string, int> RequiredMaterials
-        {
-            get => _requiredMaterials;
-            private set => _requiredMaterials = value;
-        }
-
-
-        /// <summary>
-        ///     How many milliseconds it'll take for the lathe to finish this recipe.
-        ///     Might lower depending on the lathe's upgrade level.
-        /// </summary>
-        [ViewVariables]
-        public TimeSpan CompleteTime => _completeTime;
+        [DataField]
+        public Dictionary<ProtoId<MaterialPrototype>, int> Materials = new();
 
-        [DataField("applyMaterialDiscount")]
+        [DataField]
         public bool ApplyMaterialDiscount = true;
 
         /// <summary>
index 81c6950f283b02fabd94c9a1a9070e84b5ce547c..d8ce0634d3e29c1aeba3626bc887327555528afd 100644 (file)
@@ -1,4 +1,5 @@
 using System.Linq;
+using Content.Shared.Lathe;
 using Content.Shared.Research.Components;
 using Content.Shared.Research.Prototypes;
 using JetBrains.Annotations;
@@ -12,6 +13,7 @@ public abstract class SharedResearchSystem : EntitySystem
 {
     [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedLatheSystem _lathe = default!;
 
     public override void Initialize()
     {
@@ -185,7 +187,7 @@ public abstract class SharedResearchSystem : EntitySystem
             var recipeProto = PrototypeManager.Index(recipe);
             description.PushNewline();
             description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry",
-                ("name",recipeProto.Name)));
+                ("name", _lathe.GetRecipeName(recipeProto))));
         }
         foreach (var generic in technology.GenericUnlocks)
         {
index b0d615fcb3bf7562b1905f3925c24228bb8f5959..93c7c22471c6f5d99b25517b4ec0eb5c63ba2efb 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Examine;
 using Content.Shared.Interaction;
+using Content.Shared.Lathe;
 using Content.Shared.Popups;
 using Content.Shared.Random.Helpers;
 using Content.Shared.Research.Components;
@@ -19,6 +20,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedResearchSystem _research = default!;
+    [Dependency] private readonly SharedLatheSystem _lathe = default!;
 
     public override void Initialize()
     {
@@ -83,8 +85,7 @@ public sealed class TechnologyDiskSystem : EntitySystem
         if (ent.Comp.Recipes != null && ent.Comp.Recipes.Count > 0)
         {
             var prototype = _protoMan.Index(ent.Comp.Recipes[0]);
-            var resultPrototype = _protoMan.Index<EntityPrototype>(prototype.Result);
-            message = Loc.GetString("tech-disk-examine", ("result", resultPrototype.Name));
+            message = Loc.GetString("tech-disk-examine", ("result", _lathe.GetRecipeName(prototype)));
 
             if (ent.Comp.Recipes.Count > 1) //idk how to do this well. sue me.
                 message += " " + Loc.GetString("tech-disk-examine-more");
diff --git a/Resources/Locale/en-US/lathe/recipes.ftl b/Resources/Locale/en-US/lathe/recipes.ftl
new file mode 100644 (file)
index 0000000..99186c5
--- /dev/null
@@ -0,0 +1,8 @@
+lathe-recipe-Medkit-name = first aid kit (empty)
+lathe-recipe-MedkitBurn-name = burn treatment kit (empty)
+lathe-recipe-MedkitToxin-name = toxin treatment kit (empty)
+lathe-recipe-MedkitO2-name = oxygen deprivation treatment kit (empty)
+lathe-recipe-MedkitBrute-name = brute trauma treatment kit (empty)
+lathe-recipe-MedkitAdvanced-name = advanced first aid kit (empty)
+lathe-recipe-MedkitRadiation-name = radiation treatment kit (empty)
+lathe-recipe-MedkitCombat-name = combat medical kit (empty)
index 71dd50d409183851be0acf8398deb390169db71b..16b67022ad516fbbacfa4a97c3de9ec901aa2738 100644 (file)
@@ -6,6 +6,9 @@ lathe-menu-search-designs = Search designs
 lathe-menu-category-all = All
 lathe-menu-search-filter = Filter:
 lathe-menu-amount = Amount:
+lathe-menu-reagent-slot-examine = It has a slot for a beaker on the side.
+lathe-reagent-dispense-no-container = Liquid pours out of {THE($name)} onto the floor!
+lathe-menu-result-reagent-display = {$reagent} ({$amount}u)
 lathe-menu-material-display = {$material} ({$amount})
 lathe-menu-tooltip-display = {$amount} of {$material}
 lathe-menu-description-display = [italic]{$description}[/italic]
index b1a2e6df1ddee37b9bec5c7c7d26095064115fcd..c414bfad5251bd9d64b6231b8f05ae2e17989c27 100644 (file)
 - type: latheRecipe
   id: Medkit
   result: Medkit
-  name: first aid kit (empty)
+  name: lathe-recipe-Medkit-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitBurn
   result: MedkitBurn
-  name: burn treatment kit (empty)
+  name: lathe-recipe-MedkitBurn-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitToxin
   result: MedkitToxin
-  name: toxin treatment kit (empty)
+  name: lathe-recipe-MedkitToxin-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitO2
   result: MedkitO2
-  name: oxygen deprivation treatment kit (empty)
+  name: lathe-recipe-MedkitO2-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitBrute
   result: MedkitBrute
-  name: brute trauma treatment kit (empty)
+  name: lathe-recipe-MedkitBrute-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitAdvanced
   result: MedkitAdvanced
-  name: advanced first aid kit (empty)
+  name: lathe-recipe-MedkitAdvanced-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitRadiation
   result: MedkitRadiation
-  name: radiation treatment kit (empty)
+  name: lathe-recipe-MedkitRadiation-name
   completetime: 2
   materials:
     Plastic: 300
 - type: latheRecipe
   id: MedkitCombat
   result: MedkitCombat
-  name: combat medical kit (empty)
+  name: lathe-recipe-MedkitCombat-name
   completetime: 2
   materials:
     Plastic: 300