From fe0414eda7eaba15154dad4603865b5701125596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= <6766154+Zumorica@users.noreply.github.com> Date: Fri, 26 Apr 2019 15:51:05 +0200 Subject: [PATCH] Lathes (#207) * Recipe stuff. * Lathe GUI and stuff * god dammit * Lathe menu works, yay. * EventArgs henk * Some work * SS14 -> Robust * More SS14 -> Robust * Lathe materials * Lathe works, Lathe GUI, Queue GUI, etc too many changes to name them here * Remove materials button, add ViewVariables and update lathe on connect * Add Autolathe RSI * Adds new recipes, fixes a few bugs. * Remove unused ScrollContainers * Use same delegate for spawn. * Removes client-side LatheComponent in favor of BoundUserInterface * Remove GetMaterial and TryGetMaterial * Use auto-properties in a few places. * Adds LatheDatabase, and a bunch of other changes * Remove useless log. * Remove lathetype from prototypes. * Turns Storage, Lathe and Database into autoproperties * Remove Hacked property from LatheRecipePrototype * Remove unneeded dependency injection from components * Refactors LatheDatabaseComponent to use ComponentState * Refactors MaterialStorageComponent to use ComponentState * Oopsie * Another oopsie * Last oopsie, I hope * Fix missing Close call. --- Content.Client/Content.Client.csproj | 5 + Content.Client/EntryPoint.cs | 13 +- .../Research/LatheBoundUserInterface.cs | 107 ++++++++ .../Research/LatheDatabaseComponent.cs | 30 +++ .../Research/MaterialStorageComponent.cs | 24 ++ Content.Client/Research/LatheMenu.cs | 255 ++++++++++++++++++ Content.Client/Research/LatheQueueMenu.cs | 149 ++++++++++ Content.Server/Content.Server.csproj | 6 +- Content.Server/EntryPoint.cs | 11 +- .../Components/Research/LatheComponent.cs | 152 +++++++++++ .../Research/LatheDatabaseComponent.cs | 60 +++++ .../Research/MaterialStorageComponent.cs | 83 ++++++ .../GameObjects/EntitySystems/LatheSystem.cs | 26 ++ Content.Shared/Content.Shared.csproj | 6 + .../Components/Materials/MaterialComponent.cs | 15 +- .../Research/SharedLatheComponent.cs | 116 ++++++++ .../Research/SharedLatheDatabaseComponent.cs | 107 ++++++++ .../SharedMaterialStorageComponent.cs | 78 ++++++ Content.Shared/GameObjects/ContentNetIDs.cs | 3 + .../Materials/Material.cs | 26 +- .../Research/LatheRecipePrototype.cs | 100 +++++++ Content.Shared/Research/RecipePrototype.cs | 65 +++++ Resources/Prototypes/Entities/Lathe.yml | 37 +++ Resources/Prototypes/Entities/Materials.yml | 8 + Resources/Prototypes/LatheRecipes/medical.yml | 17 ++ Resources/Prototypes/LatheRecipes/misc.yml | 17 ++ Resources/Prototypes/LatheRecipes/sheet.yml | 15 ++ Resources/Prototypes/LatheRecipes/tools.yml | 59 ++++ Resources/Prototypes/materials.yml | 2 + .../Buildings/autolathe.rsi/closing.png | Bin 0 -> 1515 bytes .../Textures/Buildings/autolathe.rsi/idle.png | Bin 0 -> 627 bytes .../Buildings/autolathe.rsi/meta.json | 1 + .../Buildings/autolathe.rsi/opening.png | Bin 0 -> 1516 bytes .../Buildings/autolathe.rsi/panel.png | Bin 0 -> 205 bytes .../Buildings/autolathe.rsi/stanok.png | Bin 0 -> 577 bytes .../Buildings/autolathe.rsi/workdone.png | Bin 0 -> 1270 bytes .../Buildings/autolathe.rsi/working.png | Bin 0 -> 986 bytes 37 files changed, 1580 insertions(+), 13 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs create mode 100644 Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs create mode 100644 Content.Client/GameObjects/Components/Research/MaterialStorageComponent.cs create mode 100644 Content.Client/Research/LatheMenu.cs create mode 100644 Content.Client/Research/LatheQueueMenu.cs create mode 100644 Content.Server/GameObjects/Components/Research/LatheComponent.cs create mode 100644 Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs create mode 100644 Content.Server/GameObjects/Components/Research/MaterialStorageComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/LatheSystem.cs rename {Content.Server => Content.Shared}/GameObjects/Components/Materials/MaterialComponent.cs (81%) create mode 100644 Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Research/SharedMaterialStorageComponent.cs rename {Content.Server => Content.Shared}/Materials/Material.cs (83%) create mode 100644 Content.Shared/Research/LatheRecipePrototype.cs create mode 100644 Content.Shared/Research/RecipePrototype.cs create mode 100644 Resources/Prototypes/Entities/Lathe.yml create mode 100644 Resources/Prototypes/LatheRecipes/medical.yml create mode 100644 Resources/Prototypes/LatheRecipes/misc.yml create mode 100644 Resources/Prototypes/LatheRecipes/sheet.yml create mode 100644 Resources/Prototypes/LatheRecipes/tools.yml create mode 100644 Resources/Textures/Buildings/autolathe.rsi/closing.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/idle.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/meta.json create mode 100644 Resources/Textures/Buildings/autolathe.rsi/opening.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/panel.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/stanok.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/workdone.png create mode 100644 Resources/Textures/Buildings/autolathe.rsi/working.png diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index fb7d05835e..0c937fa2d0 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -86,6 +86,9 @@ + + + @@ -114,6 +117,8 @@ + + diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index d1c4affc94..5caf8bec12 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -28,11 +28,15 @@ using System; using Content.Client.Chat; using Content.Client.GameObjects.Components; using Content.Client.GameObjects.Components.Mobs; +using Content.Client.GameObjects.Components.Research; using Content.Client.GameObjects.Components.Sound; using Content.Client.Interfaces.Chat; +using Content.Client.Research; using Content.Client.UserInterface; using Content.Shared.GameObjects.Components.Markers; +using Content.Shared.GameObjects.Components.Materials; using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Research; using Robust.Client.Interfaces.UserInterface; using Robust.Shared.Log; @@ -72,7 +76,6 @@ namespace Content.Client factory.RegisterIgnore("Storeable"); - factory.RegisterIgnore("Material"); factory.RegisterIgnore("Stack"); factory.Register(); @@ -86,7 +89,10 @@ namespace Content.Client factory.Register(); factory.Register(); factory.Register(); + factory.Register(); factory.Register(); + factory.Register(); + factory.RegisterReference(); factory.RegisterReference(); @@ -114,6 +120,11 @@ namespace Content.Client factory.Register(); + factory.Register(); + factory.Register(); + + factory.RegisterReference(); + factory.Register(); factory.RegisterReference(); diff --git a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs new file mode 100644 index 0000000000..c8fbeb063a --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using Content.Client.Research; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Client.Interfaces.Graphics; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.Research +{ + public class LatheBoundUserInterface : BoundUserInterface + { +#pragma warning disable CS0649 + [Dependency] + private IDisplayManager _displayManager; + [Dependency] + private IPrototypeManager _prototypeManager; +#pragma warning restore + [ViewVariables] + private LatheMenu menu; + [ViewVariables] + private LatheQueueMenu queueMenu; + + public MaterialStorageComponent Storage { get; private set; } + public SharedLatheComponent Lathe { get; private set; } + public LatheDatabaseComponent Database { get; private set; } + + [ViewVariables] + public Queue QueuedRecipes => _queuedRecipes; + private Queue _queuedRecipes = new Queue(); + + public LatheBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + SendMessage(new SharedLatheComponent.LatheSyncRequestMessage()); + } + + protected override void Open() + { + base.Open(); + IoCManager.InjectDependencies(this); + + if (!Owner.Owner.TryGetComponent(out MaterialStorageComponent storage) + || !Owner.Owner.TryGetComponent(out SharedLatheComponent lathe) + || !Owner.Owner.TryGetComponent(out LatheDatabaseComponent database)) return; + + Storage = storage; + Lathe = lathe; + Database = database; + + menu = new LatheMenu(_displayManager) {Owner = this}; + queueMenu = new LatheQueueMenu(_displayManager) { Owner = this }; + + menu.OnClose += Close; + + menu.AddToScreen(); + menu.Populate(); + menu.PopulateMaterials(); + queueMenu.AddToScreen(); + + menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); }; + + storage.OnMaterialStorageChanged += menu.PopulateDisabled; + storage.OnMaterialStorageChanged += menu.PopulateMaterials; + + menu.OpenCentered(); + } + + public void Queue(LatheRecipePrototype recipe, int quantity = 1) + { + SendMessage(new SharedLatheComponent.LatheQueueRecipeMessage(recipe.ID, quantity)); + } + + protected override void ReceiveMessage(BoundUserInterfaceMessage message) + { + switch (message) + { + case SharedLatheComponent.LatheProducingRecipeMessage msg: + if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break; + queueMenu.SetInfo(recipe); + break; + case SharedLatheComponent.LatheStoppedProducingRecipeMessage msg: + queueMenu.ClearInfo(); + break; + case SharedLatheComponent.LatheFullQueueMessage msg: + _queuedRecipes.Clear(); + foreach (var id in msg.Recipes) + { + if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break; + _queuedRecipes.Enqueue(recipePrototype); + } + queueMenu.PopulateList(); + break; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + menu?.Dispose(); + queueMenu?.Dispose(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs b/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs new file mode 100644 index 0000000000..b40d96319d --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/LatheDatabaseComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; + +namespace Content.Client.GameObjects.Components.Research +{ + public class LatheDatabaseComponent : SharedLatheDatabaseComponent + { +#pragma warning disable CS0649 + [Dependency] + private IPrototypeManager _prototypeManager; +#pragma warning restore + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + if (!(curState is LatheDatabaseState state)) return; + Clear(); + foreach (var ID in state.Recipes) + { + if(!_prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe)) continue; + AddRecipe(recipe); + } + } + } +} diff --git a/Content.Client/GameObjects/Components/Research/MaterialStorageComponent.cs b/Content.Client/GameObjects/Components/Research/MaterialStorageComponent.cs new file mode 100644 index 0000000000..de32c6f55c --- /dev/null +++ b/Content.Client/GameObjects/Components/Research/MaterialStorageComponent.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; + +namespace Content.Client.GameObjects.Components.Research +{ + public class MaterialStorageComponent : SharedMaterialStorageComponent + { + protected override Dictionary Storage { get; set; } = new Dictionary(); + + public event Action OnMaterialStorageChanged; + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + if (!(curState is MaterialStorageState state)) return; + Storage = state.Storage; + OnMaterialStorageChanged?.Invoke(); + } + } +} diff --git a/Content.Client/Research/LatheMenu.cs b/Content.Client/Research/LatheMenu.cs new file mode 100644 index 0000000000..5a7ab389a7 --- /dev/null +++ b/Content.Client/Research/LatheMenu.cs @@ -0,0 +1,255 @@ +using System.Collections.Generic; +using Content.Client.GameObjects.Components.Research; +using Content.Shared.Materials; +using Content.Shared.Research; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.Utility; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Timers; +using Robust.Shared.Utility; + +namespace Content.Client.Research +{ + public class LatheMenu : SS14Window + { +#pragma warning disable CS0649 + [Dependency] + private IPrototypeManager PrototypeManager; + [Dependency] + private IResourceCache ResourceCache; +#pragma warning restore + + private ItemList Items; + private ItemList Materials; + private LineEdit AmountLineEdit; + private LineEdit SearchBar; + public Button QueueButton; + protected override Vector2? CustomSize => (300, 450); + + public LatheBoundUserInterface Owner { get; set; } + + private List _recipes = new List(); + private List _shownRecipes = new List(); + + public LatheMenu(IDisplayManager displayMan) : base(displayMan) + { + } + + public LatheMenu(IDisplayManager displayMan, string name) : base(displayMan, name) + { + } + + protected override void Initialize() + { + base.Initialize(); + IoCManager.InjectDependencies(this); + + HideOnClose = true; + Title = "Lathe Menu"; + Visible = false; + + var margin = new MarginContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + MarginTop = 5f, + MarginLeft = 5f, + MarginRight = -5f, + MarginBottom = -5f, + }; + + margin.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var vbox = new VBoxContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SeparationOverride = 5, + }; + + vbox.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var hboxButtons = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + }; + + QueueButton = new Button() + { + Text = "Queue", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + }; + + var spacer = new Control() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3, + }; + + spacer.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var hboxFilter = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1 + }; + + SearchBar = new LineEdit() + { + PlaceHolder = "Search Designs", + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3 + }; + + SearchBar.OnTextChanged += Populate; + + var filterButton = new Button() + { + Text = "Filter", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + Disabled = true, + }; + + Items = new ItemList() + { + SizeFlagsStretchRatio = 8, + SizeFlagsVertical = SizeFlags.FillExpand, + }; + + + + Items.OnItemSelected += ItemSelected; + + AmountLineEdit = new LineEdit() + { + PlaceHolder = "Amount", + Text = "1", + SizeFlagsHorizontal = SizeFlags.FillExpand, + }; + + AmountLineEdit.OnTextChanged += PopulateDisabled; + + Materials = new ItemList() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3 + }; + + hboxButtons.AddChild(spacer); + hboxButtons.AddChild(QueueButton); + + hboxFilter.AddChild(SearchBar); + hboxFilter.AddChild(filterButton); + + vbox.AddChild(hboxButtons); + vbox.AddChild(hboxFilter); + vbox.AddChild(Items); + vbox.AddChild(AmountLineEdit); + vbox.AddChild(Materials); + + margin.AddChild(vbox); + + Contents.AddChild(margin); + } + + public void ItemSelected(ItemList.ItemListSelectedEventArgs args) + { + int.TryParse(AmountLineEdit.Text, out var quantity); + if (quantity <= 0) quantity = 1; + Owner.Queue(_shownRecipes[args.ItemIndex], quantity); + Items.SelectMode = ItemList.ItemListSelectMode.None; + Timer.Spawn(100, () => + { + Items.Unselect(args.ItemIndex); + Items.SelectMode = ItemList.ItemListSelectMode.Single; + }); + } + + public void PopulateMaterials() + { + Materials.Clear(); + + foreach (var (id, amount) in Owner.Storage) + { + if (!PrototypeManager.TryIndex(id, out MaterialPrototype materialPrototype)) continue; + var material = materialPrototype.Material; + Materials.AddItem($"{material.Name} {amount} cm3", material.Icon.Frame0(), false); + } + } + + /// + /// Disables or enables shown recipes depending on whether there are enough materials for it or not. + /// + public void PopulateDisabled() + { + int.TryParse(AmountLineEdit.Text, out var quantity); + if (quantity <= 0) quantity = 1; + for (var i = 0; i < _shownRecipes.Count; i++) + { + var prototype = _shownRecipes[i]; + Items.SetItemDisabled(i, !Owner.Lathe.CanProduce(prototype, quantity)); + } + } + + /// + public void PopulateDisabled(LineEdit.LineEditEventArgs args) + { + PopulateDisabled(); + } + + /// + /// Adds shown recipes to the ItemList control. + /// + public void PopulateList() + { + Items.Clear(); + for (var i = 0; i < _shownRecipes.Count; i++) + { + var prototype = _shownRecipes[i]; + Items.AddItem(prototype.Name, prototype.Icon.Frame0()); + } + + PopulateDisabled(); + } + + /// + /// Populates the list of recipes that will actually be shown, using the current filters. + /// + public void Populate() + { + _shownRecipes.Clear(); + + foreach (var prototype in Owner.Database) + { + if (SearchBar.Text.Trim().Length != 0) + { + if (prototype.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant())) + _shownRecipes.Add(prototype); + continue; + } + + _shownRecipes.Add(prototype); + } + + PopulateList(); + } + + /// + public void Populate(LineEdit.LineEditEventArgs args) + { + Populate(); + } + } +} diff --git a/Content.Client/Research/LatheQueueMenu.cs b/Content.Client/Research/LatheQueueMenu.cs new file mode 100644 index 0000000000..64b617ad78 --- /dev/null +++ b/Content.Client/Research/LatheQueueMenu.cs @@ -0,0 +1,149 @@ +using Content.Client.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Client.Graphics; +using Robust.Client.Graphics.Drawing; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.Utility; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; + +namespace Content.Client.Research +{ + public class LatheQueueMenu : SS14Window + { + protected override Vector2? CustomSize => (300, 450); + + public LatheBoundUserInterface Owner { get; set; } + + [ViewVariables] + private ItemList QueueList; + private Label Name; + private Label Description; + private TextureRect Icon; + + public LatheQueueMenu(IDisplayManager displayManager) : base(displayManager) + { + + } + + protected override void Initialize() + { + base.Initialize(); + + HideOnClose = true; + Title = "Lathe Queue"; + Visible = false; + + var margin = new MarginContainer() + { + MarginTop = 5f, + MarginLeft = 5f, + MarginRight = -5f, + MarginBottom = -5f, + }; + + margin.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var vbox = new VBoxContainer(); + + vbox.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var descMargin = new MarginContainer() + { + MarginTop = 5f, + MarginLeft = 5f, + MarginRight = -5f, + MarginBottom = -5f, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 2, + }; + + var hbox = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + }; + + Icon = new TextureRect() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 2, + }; + + var vboxInfo = new VBoxContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3, + }; + + Name = new Label() + { + RectClipContent = true, + SizeFlagsHorizontal = SizeFlags.Fill, + }; + + Description = new Label() + { + RectClipContent = true, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.Fill, + + }; + + QueueList = new ItemList() + { + SizeFlagsHorizontal = SizeFlags.Fill, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 3, + SelectMode = ItemList.ItemListSelectMode.None + }; + + vboxInfo.AddChild(Name); + vboxInfo.AddChild(Description); + + hbox.AddChild(Icon); + hbox.AddChild(vboxInfo); + + descMargin.AddChild(hbox); + + vbox.AddChild(descMargin); + vbox.AddChild(QueueList); + + margin.AddChild(vbox); + + Contents.AddChild(margin); + + ClearInfo(); + } + + public void SetInfo(LatheRecipePrototype recipe) + { + Icon.Texture = recipe.Icon.Frame0(); + if (recipe.Name != null) + Name.Text = recipe.Name; + if (recipe.Description != null) + Description.Text = recipe.Description; + } + + public void ClearInfo() + { + Icon.Texture = Texture.Transparent; + Name.Text = "-------"; + Description.Text = "Not producing anything."; + } + + public void PopulateList() + { + QueueList.Clear(); + var idx = 1; + foreach (var recipe in Owner.QueuedRecipes) + { + QueueList.AddItem($"{idx}. {recipe.Name}", recipe.Icon.Frame0(), false); + idx++; + } + } + } +} diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 5fd73f4365..b08832352c 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -108,6 +108,9 @@ + + + @@ -125,6 +128,7 @@ + @@ -198,8 +202,6 @@ - - diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 885862d97a..a4ab7fccd8 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -26,7 +26,6 @@ using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan; using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile; using Content.Server.GameObjects.Components.Projectiles; using Content.Server.GameObjects.Components.Weapon.Melee; -using Content.Server.GameObjects.Components.Materials; using Content.Server.GameObjects.Components.Stack; using Content.Server.GameObjects.Components.Construction; using Content.Server.GameObjects.Components.Mobs; @@ -40,6 +39,7 @@ using Content.Server.GameObjects.Components.Weapon.Ranged; using Content.Server.GameTicking; using Content.Server.Interfaces; using Content.Server.Interfaces.GameTicking; +using Content.Shared.GameObjects.Components.Materials; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Markers; using Content.Shared.GameObjects.Components.Mobs; @@ -50,6 +50,8 @@ using Content.Server.GameObjects.Components.Destructible; using Content.Server.GameObjects.Components.Movement; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects.Components.Movement; +using Content.Server.GameObjects.Components.Research; +using Content.Shared.GameObjects.Components.Research; namespace Content.Server { @@ -127,6 +129,8 @@ namespace Content.Server factory.Register(); factory.Register(); factory.Register(); + factory.Register(); + factory.RegisterReference(); factory.Register(); factory.Register(); @@ -139,6 +143,11 @@ namespace Content.Server factory.Register(); factory.RegisterReference(); + factory.Register(); + factory.Register(); + + factory.RegisterReference(); + factory.Register(); factory.Register(); diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs new file mode 100644 index 0000000000..5e55075e53 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Stack; +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Materials; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Timers; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Research +{ + public class LatheComponent : SharedLatheComponent, IAttackHand, IAttackBy, IActivate + { + public const int VolumePerSheet = 3750; + + private BoundUserInterface _userInterface; + + [ViewVariables] + public Queue Queue { get; } = new Queue(); + + [ViewVariables] + public bool Producing { get; private set; } = false; + + private LatheRecipePrototype _producingRecipe = null; + + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent().GetBoundUserInterface(LatheUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + private void UserInterfaceOnOnReceiveMessage(BoundUserInterfaceMessage message) + { + switch (message) + { + case LatheQueueRecipeMessage msg: + _prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe); + if (recipe != null) + for (var i = 0; i < msg.Quantity; i++) + { + Queue.Enqueue(recipe); + _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + } + break; + case LatheSyncRequestMessage msg: + if (!Owner.TryGetComponent(out MaterialStorageComponent storage)) return; + _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + if (_producingRecipe != null) + _userInterface.SendMessage(new LatheProducingRecipeMessage(_producingRecipe.ID)); + break; + } + } + + internal bool Produce(LatheRecipePrototype recipe) + { + if (Producing || !CanProduce(recipe) || !Owner.TryGetComponent(out MaterialStorageComponent storage)) return false; + + _userInterface.SendMessage(new LatheFullQueueMessage(GetIDQueue())); + + Producing = true; + _producingRecipe = recipe; + + foreach (var (material, amount) in recipe.RequiredMaterials) + { + // This should always return true, otherwise CanProduce fucked up. + storage.RemoveMaterial(material, amount); + } + + _userInterface.SendMessage(new LatheProducingRecipeMessage(recipe.ID)); + + Timer.Spawn(recipe.CompleteTime, () => + { + Producing = false; + _producingRecipe = null; + Owner.EntityManager.TrySpawnEntityAt(recipe.Result, Owner.Transform.GridPosition, out var entity); + _userInterface.SendMessage(new LatheStoppedProducingRecipeMessage()); + }); + + return true; + } + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return; + + _userInterface.Open(actor.playerSession); + return; + } + + bool IAttackHand.AttackHand(AttackHandEventArgs eventArgs) + { + + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return false; + + _userInterface.Open(actor.playerSession); + return true; + } + + bool IAttackBy.AttackBy(AttackByEventArgs eventArgs) + { + if (!Owner.TryGetComponent(out MaterialStorageComponent storage) + || !eventArgs.AttackWith.TryGetComponent(out MaterialComponent material)) return false; + + var multiplier = 1; + + if (eventArgs.AttackWith.TryGetComponent(out StackComponent stack)) multiplier = stack.Count; + + var totalAmount = 0; + + // Check if it can insert all materials. + foreach (var mat in material.MaterialTypes.Values) + { + // TODO: Change how MaterialComponent works so this is not hard-coded. + if (!storage.CanInsertMaterial(mat.ID, VolumePerSheet * multiplier)) return false; + totalAmount += VolumePerSheet * multiplier; + } + + // Check if it can take ALL of the material's volume. + if (storage.CanTakeAmount(totalAmount)) return false; + + foreach (var mat in material.MaterialTypes.Values) + { + + storage.InsertMaterial(mat.ID, VolumePerSheet * multiplier); + } + + eventArgs.AttackWith.Delete(); + + return false; + } + + private Queue GetIDQueue() + { + var queue = new Queue(); + foreach (var recipePrototype in Queue) + { + queue.Enqueue(recipePrototype.ID); + } + + return queue; + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs b/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs new file mode 100644 index 0000000000..b0d6c85e37 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/LatheDatabaseComponent.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Research; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Research +{ + public class LatheDatabaseComponent : SharedLatheDatabaseComponent + { + /// + /// Whether new recipes can be added to this database or not. + /// + public bool Static => _static; + private bool _static = false; + + public override ComponentState GetComponentState() + { + return new LatheDatabaseState(GetRecipeIdList()); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _static, "static", false); + } + + public override void Clear() + { + if (Static) return; + Dirty(); + } + + public override void AddRecipe(LatheRecipePrototype recipe) + { + if (Static) return; + Dirty(); + } + + public override bool RemoveRecipe(LatheRecipePrototype recipe) + { + if (Static || !base.RemoveRecipe(recipe)) return false; + Dirty(); + return true; + } + + private List GetRecipeIdList() + { + var list = new List(); + + foreach (var recipe in this) + { + list.Add(recipe.ID); + } + + return list; + } + } +} diff --git a/Content.Server/GameObjects/Components/Research/MaterialStorageComponent.cs b/Content.Server/GameObjects/Components/Research/MaterialStorageComponent.cs new file mode 100644 index 0000000000..2d1d22f061 --- /dev/null +++ b/Content.Server/GameObjects/Components/Research/MaterialStorageComponent.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Serialization; + +namespace Content.Server.GameObjects.Components.Research +{ + public class MaterialStorageComponent : SharedMaterialStorageComponent + { + protected override Dictionary Storage { get; set; } = new Dictionary(); + + /// + /// How much material the storage can store in total. + /// + public int StorageLimit => _storageLimit; + private int _storageLimit; + + public override ComponentState GetComponentState() + { + return new MaterialStorageState(Storage); + } + + /// + /// Checks if the storage can take a volume of material without surpassing its own limits. + /// + /// The volume of material + /// + public bool CanTakeAmount(int amount) + { + return CurrentAmount + amount <= StorageLimit; + } + + /// + /// Checks if it can insert a material. + /// + /// Material ID + /// How much to insert + /// Whether it can insert the material or not. + public bool CanInsertMaterial(string ID, int amount) + { + return (CanTakeAmount(amount) || StorageLimit < 0) && (!Storage.ContainsKey(ID) || Storage[ID] + amount >= 0); + } + + /// + /// Inserts material into the storage. + /// + /// Material ID + /// How much to insert + /// Whether it inserted it or not. + public bool InsertMaterial(string ID, int amount) + { + if (!CanInsertMaterial(ID, amount)) return false; + + if (!Storage.ContainsKey(ID)) + Storage.Add(ID, 0); + + Storage[ID] += amount; + + Dirty(); + + return true; + } + + /// + /// Removes material from the storage. + /// + /// Material ID + /// How much to remove + /// Whether it removed it or not. + public bool RemoveMaterial(string ID, int amount) + { + return InsertMaterial(ID, -amount); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _storageLimit, "StorageLimit", -1); + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/LatheSystem.cs b/Content.Server/GameObjects/EntitySystems/LatheSystem.cs new file mode 100644 index 0000000000..b85a251a4a --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/LatheSystem.cs @@ -0,0 +1,26 @@ +using Content.Server.GameObjects.Components.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class LatheSystem : EntitySystem + { + public override void Initialize() + { + EntityQuery = new TypeEntityQuery(typeof(LatheComponent)); + } + + public override void Update(float frameTime) + { + foreach (var entity in RelevantEntities) + { + var comp = entity.GetComponent(); + if (comp.Producing == false && comp.Queue.Count > 0) + { + comp.Produce(comp.Queue.Dequeue()); + } + } + } + } +} diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index 00dda7b7e2..94c90549c4 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -74,9 +74,13 @@ + + + + @@ -90,6 +94,7 @@ + @@ -100,6 +105,7 @@ + diff --git a/Content.Server/GameObjects/Components/Materials/MaterialComponent.cs b/Content.Shared/GameObjects/Components/Materials/MaterialComponent.cs similarity index 81% rename from Content.Server/GameObjects/Components/Materials/MaterialComponent.cs rename to Content.Shared/GameObjects/Components/Materials/MaterialComponent.cs index f1a3ae2edc..2514c9cdfc 100644 --- a/Content.Server/GameObjects/Components/Materials/MaterialComponent.cs +++ b/Content.Shared/GameObjects/Components/Materials/MaterialComponent.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Content.Server.Materials; +using Content.Shared.Materials; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Serialization; @@ -8,7 +8,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; -namespace Content.Server.GameObjects.Components.Materials +namespace Content.Shared.GameObjects.Components.Materials { /// /// Component to store data such as "this object is made out of steel". @@ -19,7 +19,8 @@ namespace Content.Server.GameObjects.Components.Materials public const string SerializationCache = "mat"; public override string Name => "Material"; - Dictionary MaterialTypes; + public Dictionary MaterialTypes => _materialTypes; + private Dictionary _materialTypes; public override void ExposeData(ObjectSerializer serializer) { @@ -33,11 +34,11 @@ namespace Content.Server.GameObjects.Components.Materials if (serializer.TryGetCacheData(SerializationCache, out Dictionary cached)) { - MaterialTypes = cached.ShallowClone(); + _materialTypes = cached.ShallowClone(); return; } - MaterialTypes = new Dictionary(); + _materialTypes = new Dictionary(); if (serializer.TryReadDataField("materials", out List list)) { @@ -46,12 +47,12 @@ namespace Content.Server.GameObjects.Components.Materials foreach (var entry in list) { var proto = protoMan.Index(entry.Value); - MaterialTypes[entry.Key] = proto.Material; + _materialTypes[entry.Key] = proto.Material; index++; } } - serializer.SetCacheData(SerializationCache, MaterialTypes.ShallowClone()); + serializer.SetCacheData(SerializationCache, _materialTypes.ShallowClone()); } class MaterialDataEntry : IExposeData diff --git a/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs new file mode 100644 index 0000000000..feb5518d5c --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedLatheComponent.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Materials; +using Content.Shared.Research; +using Mono.Cecil; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedLatheComponent : Component + { + public override string Name => "Lathe"; + public override uint? NetID => ContentNetIDs.LATHE; + +#pragma warning disable CS0649 + [Dependency] + protected IPrototypeManager _prototypeManager; +#pragma warning restore + + public bool CanProduce(LatheRecipePrototype recipe, int quantity = 1) + { + if (!Owner.TryGetComponent(out SharedMaterialStorageComponent storage) + || !Owner.TryGetComponent(out SharedLatheDatabaseComponent database)) return false; + + if (!database.Contains(recipe)) return false; + + foreach (var (material, amount) in recipe.RequiredMaterials) + { + if (storage[material] <= (amount * quantity)) return false; + } + + return true; + } + + public bool CanProduce(string ID, int quantity = 1) + { + return _prototypeManager.TryIndex(ID, out LatheRecipePrototype recipe) && CanProduce(recipe, quantity); + } + + /// + /// Sent to the server to sync material storage and the recipe queue. + /// + [Serializable, NetSerializable] + public class LatheSyncRequestMessage : BoundUserInterfaceMessage + { + public LatheSyncRequestMessage() + { + } + } + + /// + /// Sent to the client when the lathe is producing a recipe. + /// + [Serializable, NetSerializable] + public class LatheProducingRecipeMessage : BoundUserInterfaceMessage + { + public readonly string ID; + public LatheProducingRecipeMessage(string id) + { + ID = id; + } + } + + /// + /// Sent to the client when the lathe stopped/finished producing a recipe. + /// + [Serializable, NetSerializable] + public class LatheStoppedProducingRecipeMessage : BoundUserInterfaceMessage + { + public LatheStoppedProducingRecipeMessage() + { + } + } + + /// + /// Sent to the client to let it know about the recipe queue. + /// + [Serializable, NetSerializable] + public class LatheFullQueueMessage : BoundUserInterfaceMessage + { + public readonly Queue Recipes; + public LatheFullQueueMessage(Queue recipes) + { + Recipes = recipes; + } + } + + /// + /// Sent to the server when a client queues a new recipe. + /// + [Serializable, NetSerializable] + public class LatheQueueRecipeMessage : BoundUserInterfaceMessage + { + public readonly string ID; + public readonly int Quantity; + public LatheQueueRecipeMessage(string id, int quantity) + { + ID = id; + Quantity = quantity; + } + } + + [NetSerializable, Serializable] + public enum LatheUiKey + { + Key, + } + } +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs new file mode 100644 index 0000000000..1471889e94 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedLatheDatabaseComponent.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Content.Shared.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedLatheDatabaseComponent : Component, IEnumerable + { + public override string Name => "LatheDatabase"; + public sealed override uint? NetID => ContentNetIDs.LATHE_DATABASE; + public sealed override Type StateType => typeof(LatheDatabaseState); + + private List _recipes = new List(); + + /// + /// Removes all recipes from the database if it's not static. + /// + /// Whether it could clear the database or not. + public virtual void Clear() + { + _recipes.Clear(); + } + + /// + /// Adds a recipe to the database if it's not static. + /// + /// The recipe to be added. + /// Whether it could be added or not + public virtual void AddRecipe(LatheRecipePrototype recipe) + { + _recipes.Add(recipe); + } + + /// + /// Removes a recipe from the database if it's not static. + /// + /// The recipe to be removed. + /// Whether it could be removed or not + public virtual bool RemoveRecipe(LatheRecipePrototype recipe) + { + return _recipes.Remove(recipe); + } + + /// + /// Returns whether the database contains the recipe or not. + /// + /// The recipe to check + /// Whether the database contained the recipe or not. + public virtual bool Contains(LatheRecipePrototype recipe) + { + return _recipes.Contains(recipe); + } + + /// + /// Returns whether the database contains the recipe or not. + /// + /// The recipe id to check + /// Whether the database contained the recipe or not. + public virtual bool Contains(string id) + { + foreach (var recipe in _recipes) + { + if (recipe.ID == id) return true; + } + return false; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + var recipes = serializer.ReadDataField("recipes", new List()); + var prototypeManager = IoCManager.Resolve(); + foreach (var id in recipes) + { + if (!prototypeManager.TryIndex(id, out LatheRecipePrototype recipe)) continue; + _recipes.Add(recipe); + } + } + + public IEnumerator GetEnumerator() + { + return _recipes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + [NetSerializable, Serializable] + public class LatheDatabaseState : ComponentState + { + public readonly List Recipes; + public LatheDatabaseState(List recipes) : base(ContentNetIDs.LATHE_DATABASE) + { + Recipes = recipes; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Research/SharedMaterialStorageComponent.cs b/Content.Shared/GameObjects/Components/Research/SharedMaterialStorageComponent.cs new file mode 100644 index 0000000000..d72dae1a75 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Research/SharedMaterialStorageComponent.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Content.Shared.Materials; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Research +{ + public class SharedMaterialStorageComponent : Component, IEnumerable> + { + [ViewVariables] + protected virtual Dictionary Storage { get; set; } + public override string Name => "MaterialStorage"; + public sealed override uint? NetID => ContentNetIDs.MATERIAL_STORAGE; + public sealed override Type StateType => typeof(MaterialStorageState); + + public int this[string ID] + { + get + { + if (!Storage.ContainsKey(ID)) + return 0; + return Storage[ID]; + } + } + + public int this[Material material] + { + get + { + var ID = material.ID; + if (!Storage.ContainsKey(ID)) + return 0; + return Storage[ID]; + } + } + + /// + /// The total volume of material stored currently. + /// + [ViewVariables] public int CurrentAmount + { + get + { + var value = 0; + + foreach (var amount in Storage.Values) + { + value += amount; + } + + return value; + } + } + + public IEnumerator> GetEnumerator() + { + return Storage.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + [NetSerializable, Serializable] + public class MaterialStorageState : ComponentState + { + public readonly Dictionary Storage; + public MaterialStorageState(Dictionary storage) : base(ContentNetIDs.MATERIAL_STORAGE) + { + Storage = storage; + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index f25e205bad..fe8a7cae1e 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -18,5 +18,8 @@ public const uint ITEM = 1013; public const uint CLOTHING = 1014; public const uint ENTITYSTORAGE = 1015; + public const uint LATHE = 1016; + public const uint LATHE_DATABASE = 1017; + public const uint MATERIAL_STORAGE = 1018; } } diff --git a/Content.Server/Materials/Material.cs b/Content.Shared/Materials/Material.cs similarity index 83% rename from Content.Server/Materials/Material.cs rename to Content.Shared/Materials/Material.cs index f1ac8f3be6..de8d8dcf68 100644 --- a/Content.Server/Materials/Material.cs +++ b/Content.Shared/Materials/Material.cs @@ -1,17 +1,18 @@ using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; -namespace Content.Server.Materials +namespace Content.Shared.Materials { /// /// Materials are read-only storage for the properties of specific materials. /// Properties should be intrinsic (or at least as much is necessary for game purposes). /// - public class Material : IExposeData +public class Material : IExposeData { public string Name => _name; private string _name = "unobtanium"; @@ -72,6 +73,26 @@ namespace Content.Server.Materials public double BluntDamage => _bluntDamage; private double _bluntDamage = 1; + /// + /// An icon used to represent the material in graphic interfaces. + /// + public SpriteSpecifier Icon => _icon; + private SpriteSpecifier _icon = SpriteSpecifier.Invalid; + + public string ID + { + get + { + var prototypeManager = IoCManager.Resolve(); + foreach (var prototype in prototypeManager.EnumeratePrototypes()) + { + if (prototype.Material == this) return prototype.ID; + } + + return null; + } + } + public void ExposeData(ObjectSerializer serializer) { serializer.DataField(ref _name, "name", "unobtanium", alwaysWrite: true); @@ -87,6 +108,7 @@ namespace Content.Server.Materials serializer.DataField(ref _hardness, "hardness", 1, alwaysWrite: true); serializer.DataField(ref _sharpDamage, "sharpdamage", 1, alwaysWrite: true); serializer.DataField(ref _bluntDamage, "bluntdamage", 1, alwaysWrite: true); + serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid, alwaysWrite: true); } } diff --git a/Content.Shared/Research/LatheRecipePrototype.cs b/Content.Shared/Research/LatheRecipePrototype.cs new file mode 100644 index 0000000000..2f82b046f0 --- /dev/null +++ b/Content.Shared/Research/LatheRecipePrototype.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Research; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Research +{ + [NetSerializable, Serializable, Prototype("latheRecipe")] + public class LatheRecipePrototype : IPrototype, IIndexedPrototype + { + private string _name; + private string _id; + private SpriteSpecifier _icon; + private string _description; + private string _result; + private int _completeTime; + private Dictionary _requiredMaterials; + + public string ID => _id; + + /// + /// Name displayed in the lathe GUI. + /// + public string Name + { + get + { + if (_name.Trim().Length != 0) return _name; + var protoMan = IoCManager.Resolve(); + if (protoMan == null) return _description; + protoMan.TryIndex(_result, out EntityPrototype prototype); + if (prototype?.Name != null) + _name = prototype.Name; + return _name; + } + } + + /// + /// Short description displayed in the lathe GUI. + /// + public string Description + { + get + { + if (_description.Trim().Length != 0) return _description; + var protoMan = IoCManager.Resolve(); + if (protoMan == null) return _description; + protoMan.TryIndex(_result, out EntityPrototype prototype); + if (prototype?.Description != null) + _description = prototype.Description; + return _description; + } + } + + /// + /// Texture path used in the lathe GUI. + /// + public SpriteSpecifier Icon => _icon; + + /// + /// The prototype name of the resulting entity when the recipe is printed. + /// + public string Result => _result; + + /// + /// The materials required to produce this recipe. + /// Takes a material ID as string. + /// + public Dictionary RequiredMaterials + { + get => _requiredMaterials; + private set => _requiredMaterials = value; + } + + + /// + /// How many milliseconds it'll take for the lathe to finish this recipe. + /// Might lower depending on the lathe's upgrade level. + /// + public int CompleteTime => _completeTime; + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _description, "description", string.Empty); + serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid); + serializer.DataField(ref _result, "result", null); + serializer.DataField(ref _completeTime, "completetime", 2500); + serializer.DataField(ref _requiredMaterials, "materials", new Dictionary()); + } + } +} diff --git a/Content.Shared/Research/RecipePrototype.cs b/Content.Shared/Research/RecipePrototype.cs new file mode 100644 index 0000000000..c0cabf2a3a --- /dev/null +++ b/Content.Shared/Research/RecipePrototype.cs @@ -0,0 +1,65 @@ +using SS14.Shared.Prototypes; +using SS14.Shared.Serialization; +using SS14.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Research +{ + [Prototype("latheRecipe")] + public class LatheRecipePrototype : IPrototype, IIndexedPrototype + { + private string _name; + private string _id; + private SpriteSpecifier _icon; + private string _description; + private string _result; + private bool _hacked; + private string _latheType; + + public string ID => _id; + + /// + /// Name displayed in the lathe GUI. + /// + public string Name => _name; + + /// + /// Short description displayed in the lathe GUI. + /// + public string Description => _description; + + /// + /// Texture path used in the lathe GUI. + /// + public SpriteSpecifier Icon => _icon; + + /// + /// The prototype name of the resulting entity when the recipe is printed. + /// + public string Result => _result; + + /// + /// Whether the lathe should be hacked to unlock this recipe. + /// + public bool Hacked => _hacked; + + /// + /// The type of lathe that'll print this recipe. + /// TODO: Replace with an enum before merging, henk! + /// + public string LatheType => _latheType; + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + _name = serializer.ReadDataField("name"); + + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _description, "description", string.Empty); + serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid); + serializer.DataField(ref _result, "result", null); + serializer.DataField(ref _hacked, "hacked", false); + serializer.DataField(ref _latheType, "lathetype", "default"); + } + } +} diff --git a/Resources/Prototypes/Entities/Lathe.yml b/Resources/Prototypes/Entities/Lathe.yml new file mode 100644 index 0000000000..e47498093a --- /dev/null +++ b/Resources/Prototypes/Entities/Lathe.yml @@ -0,0 +1,37 @@ +- type: entity + id: autolathe + name: "Autolathe" + components: + - type: Clickable + - type: Sprite + sprite: Buildings/autolathe.rsi + state: idle + - type: Icon + sprite: Buildings/autolathe.rsi + state: idle + - type: BoundingBox + - type: Collidable + - type: SnapGrid + offset: Center + - type: Lathe + - type: LatheDatabase + static: true + recipes: + - Brutepack + - Ointment + - LightTube + - LightBulb + - MetalStack + - GlassStack + - Wirecutter + - Screwdriver + - Welder + - Wrench + - CableStack + - Crowbar + - Multitool + - type: MaterialStorage + - type: UserInterface + interfaces: + - key: enum.LatheUiKey.Key + type: LatheBoundUserInterface \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Materials.yml b/Resources/Prototypes/Entities/Materials.yml index 57539cb263..9ccce4bc94 100644 --- a/Resources/Prototypes/Entities/Materials.yml +++ b/Resources/Prototypes/Entities/Materials.yml @@ -45,6 +45,14 @@ texture: Objects/sheet_glass.png - type: Icon texture: Objects/sheet_glass.png + +- type: entity + id: GlassSheet1 + name: Glass Sheet 1 + parent: GlassStack + components: + - type: Stack + count: 1 - type: entity name: Cable Coil diff --git a/Resources/Prototypes/LatheRecipes/medical.yml b/Resources/Prototypes/LatheRecipes/medical.yml new file mode 100644 index 0000000000..a79746fc8e --- /dev/null +++ b/Resources/Prototypes/LatheRecipes/medical.yml @@ -0,0 +1,17 @@ +- type: latheRecipe + id: Brutepack + icon: Objects/brutepack.png + result: Brutepack + completetime: 500 + materials: + steel: 400 + glass: 125 + +- type: latheRecipe + id: Ointment + icon: Objects/ointment.png + result: Ointment + completetime: 500 + materials: + steel: 400 + glass: 125 \ No newline at end of file diff --git a/Resources/Prototypes/LatheRecipes/misc.yml b/Resources/Prototypes/LatheRecipes/misc.yml new file mode 100644 index 0000000000..e27cc5404d --- /dev/null +++ b/Resources/Prototypes/LatheRecipes/misc.yml @@ -0,0 +1,17 @@ +- type: latheRecipe + id: LightTube + icon: Objects/light_tube.rsi/normal.png + result: LightTube + completetime: 500 + materials: + steel: 60 + glass: 100 + +- type: latheRecipe + id: LightBulb + icon: Objects/light_bulb.rsi/normal.png + result: LightBulb + completetime: 500 + materials: + steel: 30 + glass: 50 \ No newline at end of file diff --git a/Resources/Prototypes/LatheRecipes/sheet.yml b/Resources/Prototypes/LatheRecipes/sheet.yml new file mode 100644 index 0000000000..ccb97f0262 --- /dev/null +++ b/Resources/Prototypes/LatheRecipes/sheet.yml @@ -0,0 +1,15 @@ +- type: latheRecipe + id: MetalStack + icon: Objects/sheet_metal.png + result: SteelSheet1 + completetime: 500 + materials: + steel: 3750 + +- type: latheRecipe + id: GlassStack + icon: Objects/sheet_glass.png + result: GlassSheet1 + completetime: 500 + materials: + glass: 3750 \ No newline at end of file diff --git a/Resources/Prototypes/LatheRecipes/tools.yml b/Resources/Prototypes/LatheRecipes/tools.yml new file mode 100644 index 0000000000..93ba2040b6 --- /dev/null +++ b/Resources/Prototypes/LatheRecipes/tools.yml @@ -0,0 +1,59 @@ +- type: latheRecipe + id: Wirecutter + icon: Objects/wirecutter.png + result: Wirecutter + completetime: 500 + materials: + steel: 80 + +- type: latheRecipe + id: Screwdriver + icon: Objects/screwdriver.png + result: Screwdriver + completetime: 500 + materials: + steel: 75 + +- type: latheRecipe + id: Welder + icon: Objects/welder.png + result: Welder + completetime: 500 + materials: + steel: 70 + glass: 30 + +- type: latheRecipe + id: Wrench + icon: Objects/wrench.png + result: Wrench + completetime: 500 + materials: + steel: 70 + glass: 30 + +- type: latheRecipe + id: CableStack + icon: Objects/cable_coil.png + result: CableStack + completetime: 500 + materials: + steel: 50 + glass: 50 + +- type: latheRecipe + id: Crowbar + icon: Objects/crowbar.png + result: Crowbar + completetime: 500 + materials: + steel: 50 + +- type: latheRecipe + id: Multitool + icon: Objects/multitool.png + result: Multitool + completetime: 500 + materials: + steel: 50 + glass: 20 \ No newline at end of file diff --git a/Resources/Prototypes/materials.yml b/Resources/Prototypes/materials.yml index 16ffd541f3..b95a13a0f0 100644 --- a/Resources/Prototypes/materials.yml +++ b/Resources/Prototypes/materials.yml @@ -2,6 +2,7 @@ id: steel name: Steel color: gray + icon: Objects/sheet_metal.png density: 7700 electricresistivity: 6.9e-7 thermalconductivity: 18 @@ -11,6 +12,7 @@ id: glass name: Glass color: '#e8f0ff33' + icon: Objects/sheet_glass.png density: 2500 electricresistivity: 1.0e+13 thermalconductivity: 0.9 diff --git a/Resources/Textures/Buildings/autolathe.rsi/closing.png b/Resources/Textures/Buildings/autolathe.rsi/closing.png new file mode 100644 index 0000000000000000000000000000000000000000..f09e99cebc833fe8e70f0c964ee3a5fecf797d84 GIT binary patch literal 1515 zcmV z&1>977{(umg&urQ4(-JROtMOwg+%nQ(10-g1Dw>XdyAo$kU(!Op~a*wC509WF`*F1 zu{G<|m)=YeB(R6DEo@0w4IyQF=*4?2J`@V>L7vTM^f4bAd9<2&zzD5I^LzC*ni;(h zUVwt`eEsE-NV&~VK3h5R$3kio~uvc*~iom-I2BSzgP@DNJ78(x6iOR#_c-H2H!!ZDG;iL=+g4_jp z2WB(BMemTM!1rd`NC2{T00FZH#s+L7Kl2^1eD1>RFH53#fU0cbr``eVj(HX31xNJq z#qUA^*v`-Fh8FEiKZZQ9kGZI9;!BMKrjrBo)&c;)`_V%H!1B$16J=g*zX1T)Z*L&* zJ?!*DK%(nr8((@LMzr7Fh?gO=LA~jn#w0lR1rTl5W9TiV&S^3 z_B2l-{3>G*<%`51GI5tV5~iWQ-2aVRiEi1%PrU<79I(y&#AMvhoiA|bzn@ZtHd|K# z#O>Ct+uH!ry$@EhFGo>60NlF0EyVAw1;V3Y89orV??o{LzK83*HH5>>LhkGW3`P;c z;W)ER+QP0-d&WxU1c>s<4Isi6IRUbK5`)Oe7nu)AG(q35KgZ*}L#(VWrk4FzdWQ2w z(#@BhkkiRmnv8qAcZmBhmVmuUYKeEG_)4;opSS=ktBb%7&#|(42H2a#>&MIftGLX) zuWkY;o{l9~pA14oKhNpp4@OZX{J|(HjDcnI_$}c0h3R|o9q`xNxp%;Hav*(Br0&e$ znKJXhhwn+nz~d)BNp`-)z2kLu2>?6&PCtLZ6UXV%I%1`vVvWC}HPd(VqPh0@f z7v}v|m2BiM0KoZ6!4Yp{38+u~X4O5N9N>>%L+klcsfkYdHG2t*UR`q6i0b^M;7Fuy z+xbq0q?Eedr2Kr-eCH1^%y<3(!+hrtFwA%U0KEV-&(T{ zhK+%+0sY^4^DY#Lq5IJXi899g0b37t5%`gj{CvVDb)O`kW{dEZ`~f04@^m{aBUyeH zgRoDEPjhIt2;cbw4D+2oz%XChA3!TFM_wj9y9_@o1{HQ0J{^M!TiG9w=aE%C0V@6k zHU?F;DxZ!)m0gKHKy@Na2Va$~gRjDN{s6;#X@3A2SbWY5IDqWjczPe6WcX|hdF(3r zBnI+(9!IM`fDH4CfV8T}&!-QJ7ekeN5%8`y-R@3wv1;iLut~M^2iT@k{s3Bo zQKXz2ZnL!f{{VOL)uqiJ;7-1pYUK~GQOf=RTD-2N8;s@a6!1DJiR8zVCxKqlyH2c literal 0 HcmV?d00001 diff --git a/Resources/Textures/Buildings/autolathe.rsi/idle.png b/Resources/Textures/Buildings/autolathe.rsi/idle.png new file mode 100644 index 0000000000000000000000000000000000000000..f30326ab239f5f5ceb97784cedbb79097f2ff49d GIT binary patch literal 627 zcmV-(0*w8MP)mAh-)X`7y;7=FvGlKfTRXiR$nHM%sUly3pG~*{6d)` z899bO_qX#AP%TcUK?NpjOhATlG(vleB+AFpNGP{ltAe;Th-;(kQJ`S!8aE;a;@X9Z zbzwwcxmMK*E5D|wAT1#FMpaa?To{LxGmr+Kmi87A>LzZ8%kyWXKxWVs7Qm|2!}0H~ zRPf`wwsH8G?uL`!RnkWE_QRXP`Jy50glVH!r`M$t$kjmE2BicV&N_ZM``CN>nG@Qz z-UG;XHop3K=oV)}pg#;j{Xm$L1DwI+B4UC}3chx3aM7Ef*;>kt-orm2aM7FK^u7l4 zqTGnrrP$9BXttJs?>A_+sz5J_=d;m!h-FTWwgKoL#|Pie3QtZ8m=%m`#}RmZC5^y1 z8YRkf?|e+R@IAO)0Ot!%W^;Z7fUZa9>V`$g?1KM+%&rq1o4R+qg%Du9K=p2sr~trP zolI4o4VeGv73m&FBis#W#pu_p3?xOU>)qXeD*t%XSgVuib1yBW;xE-e1vr~*=%@ey N002ovPDHLkV1jT*CEWl3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Buildings/autolathe.rsi/meta.json b/Resources/Textures/Buildings/autolathe.rsi/meta.json new file mode 100644 index 0000000000..9ec8665236 --- /dev/null +++ b/Resources/Textures/Buildings/autolathe.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/machines/excelsior/autolathe.dmi", "states": [{"name": "closing", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "idle", "directions": 1, "delays": [[1.0]]}, {"name": "opening", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "panel", "directions": 1, "delays": [[1.0]]}, {"name": "stanok", "directions": 1, "delays": [[1.0]]}, {"name": "workdone", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.5, 0.1, 0.1, 0.5]]}, {"name": "working", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1]]}]} \ No newline at end of file diff --git a/Resources/Textures/Buildings/autolathe.rsi/opening.png b/Resources/Textures/Buildings/autolathe.rsi/opening.png new file mode 100644 index 0000000000000000000000000000000000000000..f29bd92306da4a0c526da8a29932e554ac8ed2c3 GIT binary patch literal 1516 zcma)6Ydq6?9RAJ9YRHLPQd#6ubLon(Yz{J)+{21YlxwI_e2lG*E=%M-6zgIUCBj^C zKZOjd+;iF7rR4TeTm9S4I&aR~^WyvcJ};i<`Fx(|#h2#fU@e1GK>`3|Z19#Y;!FHJ zny|_yumfFfPQG96mB(YKASVW@)fV@ zh@p|GYM+wZxrL^xcMBR3`*9YrTyl(>YuNW*j#A{p6`GIp^%vaX#=yRT0(L$|@OIuN z+er8bjVbbAvJva(fu&HpDn$=LxheH#LW3E$+YnzbhLIK+DGd zD&k1R#(|E6^g|k(J|NQ?v)K?bj*2&LP+8mqD`EkO*(POFSt5DjFt7V_xD`eHEem+p zzP@`5&WER?ZIQ|IGs99(f!CbW&4)pV-fG`0{)m^m{z~R7Y-(at_j3TszAtSCymf0G z`=x%o{!$A81+FU)2!0Z?w>S#K1t}lC_NB2IZkoR z`8CFE3#@;2XRH(P-Z0*iZXE=@L7jnv__!$)1PR++XXFjHVlUZFd33-CvY9rM@`rMbk2>hxAO!mHOtt_%om;>cKhq4m;6p|C-(1N?tzP2Z z8Sh;VdaEf`l&xI%o;{p+krV9m`bJiBf3Ml%<%Rq$%f2L?K{={^Q&dF4Ouxd0_2SFY zb>vnpmn5}1FxCreJ>xT^T@{f~iBez9hcM7Ui~pwVx#`cVoA2SZC7($B2>WTAY&thI zRIeC&p~;rfGiNN&;%lAlU=Px+)`Ipt(ig^ai9Fvg3QK$YOEi@PT^q$*Nr=C5$2}cf zz)=BN@G;(C-=17gf0-(OZ3`19FZRmgMVtGN_@;dYOEbfXua!UW6?nlv$fL&XLT?`pbEI%>t%Q-=X^GHAP;*hI=0O0-<`K#ZE!TfcHFB^M?6cgz zc3ZXer3bxIg)mm5;MLK$Xu@TuRk~Eq?Za{nxj*5$YYo`uL;nY0tFcs0lD7Keo9b(8 zg8H4#($K*td_w5^gX%Ni5btVNKvi*c%!on`1FM>RHe|7@)34!_G9$u80CG>8mkWWJ zQU0yz4y8N>gVkr}cq4TAoPQq*mpI)q(!nY81H>f91&672t+SEAta;L7RvV;A$v=?)OIOrlu~Kx`gQSxZ4B8-AWu#K8{YsSIfw%Oemfsar{eqd^S5v|F zq#EThMDrz%;1)f>>R|SCVR`!5y{}Qb(|0%y>W%hfrp~a*<&XI2(Cv-i5uUTL6zaME zz&aE?C>Tc(3F9ayxkkEXcLA1|e}p8Y#`M|?;Iq!mHAKx57e3s}JqUu87H0?n-OYqT z4M8)p&6?dASfZ*yg_xY0g~b^f<(swH>FQBHLmk}+J07)t8Np``V9;GCoW_)F1mTnr z>?i#z%)Ct!j9{lJ}#u-()^{7r6qq*sPuZ<8QBQuP9YeEqCw}=QGMHC z6wyR%nAwKn72ArfPBK>!HAOB0xL1Zaz;omr{&xiC3L-j&@LpscHtv8*F|`+eabRQR KU|ISn@!CH}75H!f literal 0 HcmV?d00001 diff --git a/Resources/Textures/Buildings/autolathe.rsi/panel.png b/Resources/Textures/Buildings/autolathe.rsi/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..5607cee9bc6afc3f2a76cdc843759b146f5ad3e4 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJDV{ElAr*6y6C_v{Cy4Yk1sZU< z9n^qw7}WKI5Evb;n7PBCQT=;>L#;{g0Q7dz+Q=qpU=n wnVI>eqC=sG%;RUt2?+@e$L&`t8gMaeJ*)V3>;GDRp!*m+UHx3vIVCg!0NOrC*#H0l literal 0 HcmV?d00001 diff --git a/Resources/Textures/Buildings/autolathe.rsi/stanok.png b/Resources/Textures/Buildings/autolathe.rsi/stanok.png new file mode 100644 index 0000000000000000000000000000000000000000..1766a7baca912c512d4faa00f390d4b875eba7c1 GIT binary patch literal 577 zcmV-H0>1r;P)p@SGY+NK3?wyts`q9BIObu0@d0-0P^ zs;ulBo**^A%SI$B&(8Kr$^;04U6w|j#QG+Njeci~B}fXW3JsuYH6z&t(Wn#U@>AcX zLUdPg8__=TPat#{iTZa?1FZU4$cob^Fc{lhk*QonR1n&RPt6H>-4!a;bYwK=dmzy3 zuJHJg1G-LR#Mw^bhY3`wY2amoN;L~~9q)ZTnsd*lb$1Rx{y4sQe&e2;8n70Op?ekh zI!&y=(pmVXk+|DlT6T(WylVMcyi5VA<>G;uumf=k476*}{nu$M@?p zw(HHEbW~NuEB8;}ii3(t4>bNUZko%!-a>UQ(EuV0-d%>HG>>LeaIjq9>c|}mCe70=Cjem_xU3nP_ZdNcd%-Hksc4*rFmz)d- zo_*oklEnBrRJKEK*UDr4dj&RcvD}-KSDM)V=gMjo`!ME|&z9Hc%=rK7#@7EI4wt9s z&vB`KwCwLZiECdMB|b@KF!tfvy>iCWJD=wXvoL&(XY&%yi@30J>!QZDz3);(9vq+7 zbVy6<-SpUrs}7xKxOR6}7yJ8b%-r|(ZAqEVJ?UOXWmTso%ih3NW2bqhpRrt24C3xR5s^opMkmkvzuZ3yUwR>vh^?Bx0-QVmV3nQ)6849 zrFU-*dfu-p`g0xIjM?(caqklH?AWi|diMHp?$-l4kGF5&;NH4OuPx+BKhv{dy9Ybi zYpj#Mvpl~Rsn^%kSGaGz(fz+upPW&)v8>b0V@%k)ihbL*H%;E9`?R?hO<7eFe&wmS z-q+WGc_J%Ml$$n)a<}iDJ_TqN!-18NYbT$5?!%^=5 zZ)O)_SXRatrEZQ`l(%b#twZt`8 zAwMhoc%R<#U!s>DSyz60zHv6s{mnvU&z@Kpe@#`LJYQeR<&{dS(Bm-g00P2uLBKU;o!u28RU`0T{Ez%FmW6iyAFAYpI1ONYT+Vb=xd&y;kXU~IAKj!DSv;oNI2Kq9o xo?+t+c?TV!XVJMf_H_b3ra%-N2#WsC@cxvLgzkj1^1$+p!PC{xWt~$(69BRgb8Y|t literal 0 HcmV?d00001 diff --git a/Resources/Textures/Buildings/autolathe.rsi/working.png b/Resources/Textures/Buildings/autolathe.rsi/working.png new file mode 100644 index 0000000000000000000000000000000000000000..4a6cc55256522f0befe5febe58730df452cc9286 GIT binary patch literal 986 zcmeAS@N?(olHy`uVBq!ia0vp^2|(-nrNrz4v>< z3ANhkU$~b&D&{^t_vTB@w>RdV-ER9==-jM-8&>WJ*ULO!{Z#0g*tD%#-K&3fiY@5+ z(JOW(XpK4ZldDZt#uv`5IQ@$?^h*AgB}e|cX}|jX$Bmnz=%{PYiIx?tk^Ij#)hX6F zOU3OJi4~SKSj*@XQEOm&Snt*HK1N}2J-7PI7uOh~V=npkFuv-IwB!Hb#8iP?Qic#M15gkFmJlf(9mgJzjVgm>CYJ$qJye;M)ADw(Qm~Iu4t9{y3d;Rs2eSwTUV#k-*ZT#_gXUX3Wm-Tz%toy(HxckDm zG^$+a+@d;0j^&<}&0Z4f->VB18CLw~G_cm|PF?zTD)XD1vi?HH`#ZQ#onBFOx6Jib zR~@6k;!;t6ySx;(d;9l^98G;_9@QBoWOne$8YMgDf=K&|OzZCLYdZfre(`@sm*)=u zn4I_KS&NF?68RN*>+c2sSkC3OAGgieV=Hp2@bBbj2SfH>fAUg2s^Qmxy&5dSY-`=; zvcF8d)BdtO;`etu!7WicI2TkGuQF%86BT81QJX>h>td!A=FC5O!?Nao;dsBxY-!%3 zU7riWzb@5#arKklwruwq>w8rCt~S4`otb;KI5u9Z;q5KKYu~#Z*3Pwfv?=tL+m03b zq6cbHSt>Sn|2!oTklp_L`0-bc5|g)WI59ixrCwXi6?wKp+iMOK^8TpQT*dh@+2&l! zn(9aMcdya^uVVR^N5AjFVdQ&MBb@0E)fZ&j0`b literal 0 HcmV?d00001