diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 8fc536090f..295d36d676 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -102,7 +102,6 @@ namespace Content.Client.Entry _componentFactory.IgnoreMissingComponents(); // Do not add to these, they are legacy. - _componentFactory.RegisterClass(); _componentFactory.RegisterClass(); _componentFactory.RegisterClass(); _componentFactory.RegisterClass(); diff --git a/Content.Client/Lathe/Components/LatheDatabaseComponent.cs b/Content.Client/Lathe/Components/LatheDatabaseComponent.cs deleted file mode 100644 index 60eb9d11f5..0000000000 --- a/Content.Client/Lathe/Components/LatheDatabaseComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; - -namespace Content.Client.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedLatheDatabaseComponent))] - public sealed class LatheDatabaseComponent : SharedLatheDatabaseComponent - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not 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/Lathe/Components/LatheVisualsComponent.cs b/Content.Client/Lathe/Components/LatheVisualsComponent.cs deleted file mode 100644 index a38f22ce20..0000000000 --- a/Content.Client/Lathe/Components/LatheVisualsComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Content.Client.Lathe; - -/// -/// Holds the idle and running state for machines to control -/// playing animations on the client. -/// -[RegisterComponent] -public sealed class LatheVisualsComponent : Component -{ - [DataField("idleState", required: true)] - public string IdleState = default!; - - [DataField("runningState", required: true)] - public string RunningState = default!; - - [ViewVariables] - [DataField("ignoreColor")] - public bool IgnoreColor; -} diff --git a/Content.Client/Lathe/Components/MaterialStorageComponent.cs b/Content.Client/Lathe/Components/MaterialStorageComponent.cs deleted file mode 100644 index b0b425a835..0000000000 --- a/Content.Client/Lathe/Components/MaterialStorageComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Shared.Lathe; -using Robust.Shared.GameObjects; - -namespace Content.Client.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedMaterialStorageComponent))] - public sealed class MaterialStorageComponent : SharedMaterialStorageComponent - { - protected override Dictionary Storage { get; set; } = new(); - - public event Action? OnMaterialStorageChanged; - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - if (curState is not MaterialStorageState state) return; - Storage = state.Storage; - OnMaterialStorageChanged?.Invoke(); - } - } -} diff --git a/Content.Client/Lathe/Components/ProtolatheDatabaseComponent.cs b/Content.Client/Lathe/Components/ProtolatheDatabaseComponent.cs deleted file mode 100644 index 5c0523647c..0000000000 --- a/Content.Client/Lathe/Components/ProtolatheDatabaseComponent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; - -namespace Content.Client.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedLatheDatabaseComponent))] - public sealed class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - /// - /// Invoked when the database gets updated. - /// - public event Action? OnDatabaseUpdated; - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not ProtolatheDatabaseState state) return; - - Clear(); - - foreach (var id in state.Recipes) - { - if(!_prototypeManager.TryIndex(id, out LatheRecipePrototype? recipe)) continue; - AddRecipe(recipe); - } - - OnDatabaseUpdated?.Invoke(); - } - } -} diff --git a/Content.Client/Lathe/LatheSystem.cs b/Content.Client/Lathe/LatheSystem.cs index 514c851fac..f63e075927 100644 --- a/Content.Client/Lathe/LatheSystem.cs +++ b/Content.Client/Lathe/LatheSystem.cs @@ -2,45 +2,62 @@ using Robust.Client.GameObjects; using Content.Shared.Lathe; using Content.Shared.Power; using Content.Client.Power; +using Content.Shared.Research.Prototypes; -namespace Content.Client.Lathe +namespace Content.Client.Lathe; + +public sealed class LatheSystem : SharedLatheSystem { - public sealed class LatheSystem : VisualizerSystem + public override void Initialize() { - protected override void OnAppearanceChange(EntityUid uid, LatheVisualsComponent component, ref AppearanceChangeEvent args) + base.Initialize(); + + SubscribeLocalEvent(OnAppearanceChange); + } + + private void OnAppearanceChange(EntityUid uid, LatheComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null) + return; + + if (args.Component.TryGetData(PowerDeviceVisuals.Powered, out bool powered) && + args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out _)) { - if (args.Sprite == null) - return; + args.Sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered); + } - if (args.Component.TryGetData(PowerDeviceVisuals.Powered, out bool powered) && - args.Sprite.LayerMapTryGet(PowerDeviceVisualLayers.Powered, out _)) + // Lathe specific stuff + if (args.Component.TryGetData(LatheVisuals.IsRunning, out bool isRunning)) + { + var state = isRunning ? component.RunningState : component.IdleState; + args.Sprite.LayerSetAnimationTime(LatheVisualLayers.IsRunning, 0f); + args.Sprite.LayerSetState(LatheVisualLayers.IsRunning, state); + } + + if (args.Component.TryGetData(LatheVisuals.IsInserting, out bool isInserting) + && args.Sprite.LayerMapTryGet(LatheVisualLayers.IsInserting, out var isInsertingLayer)) + { + if (args.Component.TryGetData(LatheVisuals.InsertingColor, out Color color) + && !component.IgnoreColor) { - args.Sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered); + args.Sprite.LayerSetColor(isInsertingLayer, color); } - // Lathe specific stuff - if (args.Component.TryGetData(LatheVisuals.IsRunning, out bool isRunning)) - { - var state = isRunning ? component.RunningState : component.IdleState; - args.Sprite.LayerSetAnimationTime(LatheVisualLayers.IsRunning, 0f); - args.Sprite.LayerSetState(LatheVisualLayers.IsRunning, state); - } - - if (args.Component.TryGetData(LatheVisuals.IsInserting, out bool isInserting) - && args.Sprite.LayerMapTryGet(LatheVisualLayers.IsInserting, out var isInsertingLayer)) - { - if (args.Component.TryGetData(LatheVisuals.InsertingColor, out Color color) - && !component.IgnoreColor) - { - args.Sprite.LayerSetColor(isInsertingLayer, color); - } - - args.Sprite.LayerSetAnimationTime(isInsertingLayer, 0f); - args.Sprite.LayerSetVisible(isInsertingLayer, isInserting); - } + args.Sprite.LayerSetAnimationTime(isInsertingLayer, 0f); + args.Sprite.LayerSetVisible(isInsertingLayer, isInserting); } } + + /// + /// Whether or not a recipe is available is not really visible to the client, + /// so it just defaults to true. + /// + protected override bool HasRecipe(EntityUid uid, LatheRecipePrototype recipe, LatheComponent component) + { + return true; + } } + public enum LatheVisualLayers : byte { IsRunning, diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs index 233c6170e8..08850896d0 100644 --- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs +++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs @@ -1,101 +1,64 @@ -using System.Collections.Generic; -using Content.Client.Lathe.Components; using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; +using JetBrains.Annotations; using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.ViewVariables; -using static Content.Shared.Lathe.SharedLatheComponent; namespace Content.Client.Lathe.UI { + [UsedImplicitly] public sealed class LatheBoundUserInterface : BoundUserInterface { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [ViewVariables] private LatheMenu? _menu; + [ViewVariables] private LatheQueueMenu? _queueMenu; - [ViewVariables] - private LatheMenu? _menu; - [ViewVariables] - private LatheQueueMenu? _queueMenu; - - public MaterialStorageComponent? Storage { get; private set; } - public SharedLatheComponent? Lathe { get; private set; } - public SharedLatheDatabaseComponent? Database { get; private set; } - - [ViewVariables] - public Queue QueuedRecipes => _queuedRecipes; - private readonly Queue _queuedRecipes = new(); + public EntityUid Lathe; public LatheBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { - SendMessage(new LatheSyncRequestMessage()); + Lathe = owner.Owner; } protected override void Open() { base.Open(); - if (!_entMan.TryGetComponent(Owner.Owner, out MaterialStorageComponent? storage) - || !_entMan.TryGetComponent(Owner.Owner, out SharedLatheComponent? lathe) - || !_entMan.TryGetComponent(Owner.Owner, out SharedLatheDatabaseComponent? database)) return; - - Storage = storage; - Lathe = lathe; - Database = database; - _menu = new LatheMenu(this); _queueMenu = new LatheQueueMenu(this); _menu.OnClose += Close; - _menu.Populate(); - _menu.PopulateMaterials(); - - _menu.QueueButton.OnPressed += (_) => { _queueMenu.OpenCentered(); }; - - _menu.ServerConnectButton.OnPressed += (_) => + _menu.OnQueueButtonPressed += _ => + { + _queueMenu.OpenCenteredLeft(); + }; + _menu.OnServerListButtonPressed += _ => { SendMessage(new LatheServerSelectionMessage()); }; - - _menu.ServerSyncButton.OnPressed += (_) => + _menu.OnServerSyncButtonPressed += _ => { SendMessage(new LatheServerSyncMessage()); }; - - storage.OnMaterialStorageChanged += _menu.PopulateDisabled; - storage.OnMaterialStorageChanged += _menu.PopulateMaterials; + _menu.RecipeQueueAction += (recipe, amount) => + { + SendMessage(new LatheQueueRecipeMessage(recipe, amount)); + }; _menu.OpenCentered(); } - public void Queue(LatheRecipePrototype recipe, int quantity = 1) + protected override void UpdateState(BoundUserInterfaceState state) { - SendMessage(new LatheQueueRecipeMessage(recipe.ID, quantity)); - } + base.UpdateState(state); - protected override void ReceiveMessage(BoundUserInterfaceMessage message) - { - switch (message) + switch (state) { - case LatheProducingRecipeMessage msg: - if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype? recipe)) break; - _queueMenu?.SetInfo(recipe); - break; - case LatheStoppedProducingRecipeMessage _: - _queueMenu?.ClearInfo(); - break; - case LatheFullQueueMessage msg: - _queuedRecipes.Clear(); - foreach (var id in msg.Recipes) - { - if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype? recipePrototype)) break; - _queuedRecipes.Enqueue(recipePrototype); - } - _queueMenu?.PopulateList(); + case LatheUpdateState msg: + if (_menu != null) + _menu.Recipes = msg.Recipes; + _menu?.PopulateRecipes(Owner.Owner); + _menu?.PopulateMaterials(Lathe); + _queueMenu?.PopulateList(msg.Queue); + _queueMenu?.SetInfo(msg.CurrentlyProducing); break; } } @@ -103,7 +66,8 @@ namespace Content.Client.Lathe.UI protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (!disposing) return; + if (!disposing) + return; _menu?.Dispose(); _queueMenu?.Dispose(); } diff --git a/Content.Client/Lathe/UI/LatheMenu.cs b/Content.Client/Lathe/UI/LatheMenu.cs deleted file mode 100644 index c76aba0168..0000000000 --- a/Content.Client/Lathe/UI/LatheMenu.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using Content.Client.Lathe.Components; -using Content.Shared.Lathe; -using Content.Shared.Materials; -using Content.Shared.Research.Prototypes; -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.Prototypes; -using static Robust.Client.UserInterface.Controls.BoxContainer; - -namespace Content.Client.Lathe.UI -{ - public sealed class LatheMenu : DefaultWindow - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - private readonly ItemList _items; - private readonly ItemList _materials; - private readonly LineEdit _amountLineEdit; - private readonly LineEdit _searchBar; - public Button QueueButton; - public Button ServerConnectButton; - public Button ServerSyncButton; - - public const float RecipeTooltipDelay = 0.5f; - - public LatheBoundUserInterface Owner { get; } - - private readonly List _shownRecipes = new(); - - public LatheMenu(LatheBoundUserInterface owner) - { - SetSize = MinSize = (300, 450); - IoCManager.InjectDependencies(this); - - Owner = owner; - - Title = Loc.GetString("lathe-menu-title"); // TODO Replace this with the name of the lathe itself - - var vBox = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - VerticalExpand = true, - SeparationOverride = 5, - }; - - var hBoxButtons = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - VerticalExpand = true, - SizeFlagsStretchRatio = 1, - }; - - QueueButton = new Button() - { - Text = Loc.GetString("lathe-menu-queue"), - TextAlign = Label.AlignMode.Center, - SizeFlagsStretchRatio = 1, - }; - - ServerConnectButton = new Button() - { - Text = Loc.GetString("lathe-menu-server-list"), - TextAlign = Label.AlignMode.Center, - SizeFlagsStretchRatio = 1, - }; - - ServerSyncButton = new Button() - { - Text = Loc.GetString("lathe-menu-sync"), - TextAlign = Label.AlignMode.Center, - SizeFlagsStretchRatio = 1, - }; - - var spacer = new Control() - { - HorizontalExpand = true, - SizeFlagsStretchRatio = 3, - }; - - var hBoxFilter = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - VerticalExpand = true, - SizeFlagsStretchRatio = 1 - }; - - _searchBar = new LineEdit() - { - PlaceHolder = Loc.GetString("lathe-menu-search-designs"), - HorizontalExpand = true, - SizeFlagsStretchRatio = 3 - }; - - _searchBar.OnTextChanged += Populate; - - var filterButton = new Button() - { - Text = Loc.GetString("lathe-menu-search-filter"), - TextAlign = Label.AlignMode.Center, - SizeFlagsStretchRatio = 1, - Disabled = true, - }; - - _items = new ItemList() - { - SizeFlagsStretchRatio = 8, - VerticalExpand = true, - SelectMode = ItemList.ItemListSelectMode.Button, - }; - - // This is a shitty hack, because item lists apparently don't actually support tooltips. Yay.. - _items.OnItemHover += (ev) => - { - ev.ItemList.HideTooltip(); - ev.ItemList.ToolTip = ev.ItemList[ev.ItemIndex].TooltipText; - }; - _items.TooltipDelay = RecipeTooltipDelay; - - _items.OnItemSelected += ItemSelected; - - _amountLineEdit = new LineEdit() - { - PlaceHolder = Loc.GetString("lathe-menu-search-amount"), - Text = "1", - HorizontalExpand = true, - }; - - _amountLineEdit.OnTextChanged += PopulateDisabled; - - _materials = new ItemList() - { - VerticalExpand = true, - SizeFlagsStretchRatio = 3 - }; - - hBoxButtons.AddChild(spacer); - if (Owner.Database is ProtolatheDatabaseComponent database) - { - hBoxButtons.AddChild(ServerConnectButton); - hBoxButtons.AddChild(ServerSyncButton); - database.OnDatabaseUpdated += Populate; - } - hBoxButtons.AddChild(QueueButton); - - hBoxFilter.AddChild(_searchBar); - hBoxFilter.AddChild(filterButton); - - vBox.AddChild(hBoxButtons); - vBox.AddChild(hBoxFilter); - vBox.AddChild(_items); - vBox.AddChild(_amountLineEdit); - vBox.AddChild(_materials); - - Contents.AddChild(vBox); - } - - public void ItemSelected(ItemList.ItemListSelectedEventArgs args) - { - args.ItemList.HideTooltip(); - args.ItemList.ToolTip = args.ItemList[args.ItemIndex].TooltipText; - - int.TryParse(_amountLineEdit.Text, out var quantity); - if (quantity <= 0) quantity = 1; - Owner.Queue(_shownRecipes[args.ItemIndex], quantity); - } - - public void PopulateMaterials() - { - _materials.Clear(); - - if (Owner.Storage == null) return; - - foreach (var (id, amount) in Owner.Storage) - { - if (!_prototypeManager.TryIndex(id, out MaterialPrototype? materialPrototype)) continue; - var material = materialPrototype; - _materials.AddItem($"{material.Name} {amount} cm³", 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[i].Disabled = !Owner.Lathe?.CanProduce(prototype, quantity) ?? true; - } - } - - /// - public void PopulateDisabled(LineEdit.LineEditEventArgs args) - { - PopulateDisabled(); - } - - /// - /// Adds shown recipes to the ItemList control. - /// - public void PopulateList() - { - _items.Clear(); - foreach (var prototype in _shownRecipes) - { - var item = _items.AddItem(prototype.Name, prototype.Icon.Frame0()); - - StringBuilder sb = new(); - bool first = true; - foreach (var (id, quantity) in prototype.RequiredMaterials) - { - if (!_prototypeManager.TryIndex(id, out var proto)) - continue; - - if (first) - first = false; - else - sb.Append("\n"); - - sb.Append(quantity.ToString()); - sb.Append(" "); - sb.Append(proto.Name); - } - - item.TooltipText = sb.ToString(); - } - - PopulateDisabled(); - } - - /// - /// Populates the list of recipes that will actually be shown, using the current filters. - /// - public void Populate() - { - _shownRecipes.Clear(); - - if (Owner.Database == null) return; - - 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/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml new file mode 100644 index 0000000000..b65cbf2766 --- /dev/null +++ b/Content.Client/Lathe/UI/LatheMenu.xaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs new file mode 100644 index 0000000000..5a68494e1f --- /dev/null +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -0,0 +1,142 @@ +using System.Text; +using Content.Shared.Lathe; +using Content.Shared.Materials; +using Content.Shared.Research.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Lathe.UI; + +[GenerateTypedNameReferences] +public sealed partial class LatheMenu : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly SpriteSystem _spriteSystem; + private readonly LatheSystem _lathe; + + public event Action? OnQueueButtonPressed; + public event Action? OnServerListButtonPressed; + public event Action? OnServerSyncButtonPressed; + public event Action? RecipeQueueAction; + + public List Recipes = new(); + private List _oldRecipesToShow = new(); + + public LatheMenu(LatheBoundUserInterface owner) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _spriteSystem = _entityManager.EntitySysManager.GetEntitySystem(); + _lathe = _entityManager.EntitySysManager.GetEntitySystem(); + + Title = _entityManager.GetComponent(owner.Lathe).EntityName; + + SearchBar.OnTextChanged += _ => + { + PopulateRecipes(owner.Lathe); + }; + AmountLineEdit.OnTextChanged += _ => + { + PopulateRecipes(owner.Lathe); + }; + + QueueButton.OnPressed += a => OnQueueButtonPressed?.Invoke(a); + ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a); + + //refresh the bui state + ServerSyncButton.OnPressed += a => OnServerSyncButtonPressed?.Invoke(a); + + if (_entityManager.TryGetComponent(owner.Lathe, out var latheComponent)) + { + if (latheComponent.DynamicRecipes == null) + { + ServerListButton.Visible = false; + ServerSyncButton.Visible = false; + } + } + } + + public void PopulateMaterials(EntityUid lathe) + { + if (!_entityManager.TryGetComponent(lathe, out var materials)) + return; + + Materials.Clear(); + foreach (var (id, amount) in materials.Storage) + { + if (!_prototypeManager.TryIndex(id, out MaterialPrototype? material)) + continue; + var mat = Loc.GetString("lathe-menu-material-display", + ("material", material.Name), ("amount", amount)); + Materials.AddItem(mat, _spriteSystem.Frame0(material.Icon), false); + } + PopulateRecipes(lathe); + } + + /// + /// Populates the list of all the recipes + /// + /// + public void PopulateRecipes(EntityUid lathe) + { + var recipesToShow = new List(); + foreach (var recipe in Recipes) + { + if (!_prototypeManager.TryIndex(recipe, out var proto)) + continue; + + if (SearchBar.Text.Trim().Length != 0) + { + if (proto.Name.ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant())) + recipesToShow.Add(proto); + } + else + { + recipesToShow.Add(proto); + } + } + + if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0) + quantity = 1; + + RecipeList.Children.Clear(); + _oldRecipesToShow = recipesToShow; + foreach (var prototype in recipesToShow) + { + StringBuilder sb = new(); + var first = true; + foreach (var (id, amount) in prototype.RequiredMaterials) + { + if (!_prototypeManager.TryIndex(id, out var proto)) + continue; + + if (first) + first = false; + else + sb.Append('\n'); + + sb.Append(amount); + sb.Append(' '); + sb.Append(proto.Name); + } + + var icon = _spriteSystem.Frame0(prototype.Icon); + var canProduce = _lathe.CanProduce(lathe, prototype, quantity); + + var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon); + control.OnButtonPressed += s => + { + if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0) + amount = 1; + RecipeQueueAction?.Invoke(s, amount); + }; + RecipeList.AddChild(control); + } + } +} diff --git a/Content.Client/Lathe/UI/LatheQueueMenu.cs b/Content.Client/Lathe/UI/LatheQueueMenu.cs deleted file mode 100644 index 8976772798..0000000000 --- a/Content.Client/Lathe/UI/LatheQueueMenu.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Content.Shared.Research.Prototypes; -using Robust.Client.Graphics; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Client.Utility; -using Robust.Shared.Localization; -using Robust.Shared.ViewVariables; -using static Robust.Client.UserInterface.Controls.BoxContainer; - -namespace Content.Client.Lathe.UI -{ - public sealed class LatheQueueMenu : DefaultWindow - { - public LatheBoundUserInterface Owner { get; set; } - - [ViewVariables] - private readonly ItemList _queueList; - private readonly Label _nameLabel; - private readonly Label _description; - private readonly TextureRect _icon; - - public LatheQueueMenu(LatheBoundUserInterface owner) - { - Owner = owner; - SetSize = MinSize = (300, 450); - Title = Loc.GetString("lathe-queue-menu-title"); - - var vBox = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }; - - var hBox = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - SizeFlagsStretchRatio = 2, - }; - - _icon = new TextureRect() - { - HorizontalExpand = true, - SizeFlagsStretchRatio = 2, - }; - - var vBoxInfo = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - VerticalExpand = true, - SizeFlagsStretchRatio = 3, - }; - - _nameLabel = new Label() - { - RectClipContent = true, - }; - - _description = new Label() - { - RectClipContent = true, - VerticalAlignment = VAlignment.Stretch, - VerticalExpand = true - - }; - - _queueList = new ItemList() - { - VerticalExpand = true, - SizeFlagsStretchRatio = 3, - SelectMode = ItemList.ItemListSelectMode.None - }; - - vBoxInfo.AddChild(_nameLabel); - vBoxInfo.AddChild(_description); - - hBox.AddChild(_icon); - hBox.AddChild(vBoxInfo); - - vBox.AddChild(hBox); - vBox.AddChild(_queueList); - - Contents.AddChild(vBox); - - ClearInfo(); - } - - public void SetInfo(LatheRecipePrototype recipe) - { - _icon.Texture = recipe.Icon.Frame0(); - if (recipe.Name != null) - _nameLabel.Text = recipe.Name; - if (recipe.Description != null) - _description.Text = recipe.Description; - } - - public void ClearInfo() - { - _icon.Texture = Texture.Transparent; - _nameLabel.Text = "-------"; - _description.Text = Loc.GetString("lathe-queue-menu-not-producing-text"); - } - - public void PopulateList() - { - _queueList.Clear(); - var idx = 1; - foreach (var recipe in Owner.QueuedRecipes) - { - _queueList.AddItem($"{idx}. {recipe.Name}", recipe.Icon.Frame0()); - idx++; - } - } - } -} diff --git a/Content.Client/Lathe/UI/LatheQueueMenu.xaml b/Content.Client/Lathe/UI/LatheQueueMenu.xaml new file mode 100644 index 0000000000..73f3b1dacd --- /dev/null +++ b/Content.Client/Lathe/UI/LatheQueueMenu.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs b/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs new file mode 100644 index 0000000000..6b09f47d22 --- /dev/null +++ b/Content.Client/Lathe/UI/LatheQueueMenu.xaml.cs @@ -0,0 +1,52 @@ +using Content.Shared.Research.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Lathe.UI +{ + [GenerateTypedNameReferences] + public sealed partial class LatheQueueMenu : DefaultWindow + { + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly SpriteSystem _spriteSystem; + + public LatheQueueMenu(LatheBoundUserInterface owner) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _spriteSystem = _entityManager.EntitySysManager.GetEntitySystem(); + + SetInfo(null); + } + + public void SetInfo(LatheRecipePrototype? recipe) + { + if (recipe != null) + { + Icon.Texture = _spriteSystem.Frame0(recipe.Icon); + NameLabel.Text = recipe.Name; + Description.Text = recipe.Description; + } + else + { + Icon.Texture = Texture.Transparent; + NameLabel.Text = string.Empty; + Description.Text = Loc.GetString("lathe-queue-menu-not-producing-text"); + } + } + + public void PopulateList(List queue) + { + QueueList.Clear(); + var idx = 1; + foreach (var recipe in queue) + { + QueueList.AddItem($"{idx}. {recipe.Name}", _spriteSystem.Frame0(recipe.Icon)); + idx++; + } + } + } +} diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml b/Content.Client/Lathe/UI/RecipeControl.xaml new file mode 100644 index 0000000000..5ef5342185 --- /dev/null +++ b/Content.Client/Lathe/UI/RecipeControl.xaml @@ -0,0 +1,15 @@ + + + diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml.cs b/Content.Client/Lathe/UI/RecipeControl.xaml.cs new file mode 100644 index 0000000000..2a17b52c66 --- /dev/null +++ b/Content.Client/Lathe/UI/RecipeControl.xaml.cs @@ -0,0 +1,28 @@ +using Content.Shared.Research.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Lathe.UI; + +[GenerateTypedNameReferences] +public sealed partial class RecipeControl : Control +{ + public Action? OnButtonPressed; + + public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null) + { + RobustXamlLoader.Load(this); + + RecipeName.Text = recipe.Name; + RecipeTexture.Texture = texture; + Button.ToolTip = tooltip; + Button.Disabled = !canProduce; + + Button.OnPressed += (_) => + { + OnButtonPressed?.Invoke(recipe.ID); + }; + } +} diff --git a/Content.Client/Materials/MaterialStorageSystem.cs b/Content.Client/Materials/MaterialStorageSystem.cs new file mode 100644 index 0000000000..352de1a3ce --- /dev/null +++ b/Content.Client/Materials/MaterialStorageSystem.cs @@ -0,0 +1,16 @@ +using Content.Shared.Materials; +using Robust.Client.GameObjects; + +namespace Content.Client.Materials; + +/// +/// This handles... +/// +public sealed class MaterialStorageSystem : SharedMaterialStorageSystem +{ + [Dependency] private readonly TransformSystem _transform = default!; + protected override void OnFinishInsertMaterialEntity(EntityUid toInsert, MaterialStorageComponent component) + { + _transform.DetachParentToNull(Transform(toInsert)); + } +} diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs index 5fe857dcf5..df96997c7a 100644 --- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs @@ -109,13 +109,6 @@ public sealed class PrototypeSaveTest "LauncherCreamPie", "GravityGenerator", "GravityGeneratorMini", - "Autolathe", - "Protolathe", - "CircuitImprinter", - "SecurityTechFab", - "MedicalTechFab", - "UniformPrinter", - "OreProcessor", "MagazinePistolSubMachineGunTopMounted", "EpinephrineChemistryBottle", "RobustHarvestChemistryBottle", diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index d8c0239c66..eed4b0e788 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -2,7 +2,7 @@ using Content.Server.Administration; using Content.Server.Body.Components; using Content.Server.Cargo.Components; -using Content.Server.Materials; +using Content.Shared.Materials; using Content.Server.Stack; using Content.Shared.Administration; using Content.Shared.MobState.Components; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 99e3e94e7a..ca990035c2 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -16,13 +16,14 @@ using Content.Server.EUI; using Content.Server.MachineLinking.System; using Content.Server.MachineLinking.Events; using Content.Server.MobState; -using Content.Server.Lathe.Components; using Content.Shared.Chemistry.Components; using Content.Server.Fluids.EntitySystems; using Content.Server.Chat.Systems; using Content.Server.Construction.Components; +using Content.Server.Materials; using Content.Server.Stack; using Content.Server.Jobs; +using Content.Shared.Materials; using Robust.Server.GameObjects; using Robust.Server.Containers; using Robust.Server.Player; @@ -54,6 +55,7 @@ namespace Content.Server.Cloning.Systems [Dependency] private readonly SpillableSystem _spillableSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly MaterialStorageSystem _material = default!; public readonly Dictionary ClonesWaitingForMind = new(); public const float EasyModeCloningCost = 0.7f; @@ -79,10 +81,7 @@ namespace Content.Server.Cloning.Systems private void OnDeconstruct(EntityUid uid, CloningPodComponent component, MachineDeconstructedEvent args) { - if (!TryComp(uid, out var storage)) - return; - - _serverStackSystem.SpawnMultiple(storage.GetMaterialAmount("Biomass"), 100, "Biomass", Transform(uid).Coordinates); + _serverStackSystem.SpawnMultiple(_material.GetMaterialAmount(uid, "Biomass"), 100, "Biomass", Transform(uid).Coordinates); } private void UpdateAppearance(CloningPodComponent clonePod) @@ -140,8 +139,7 @@ namespace Content.Server.Cloning.Systems if (!args.IsInDetailsRange || !_powerReceiverSystem.IsPowered(uid)) return; - if (TryComp(uid, out var storage)) - args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", storage.GetMaterialAmount("Biomass")))); + args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, "Biomass")))); } public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Mind.Mind mind, CloningPodComponent? clonePod) @@ -170,9 +168,6 @@ namespace Content.Server.Cloning.Systems if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. - if (!TryComp(clonePod.Owner, out var podStorage)) - return false; - if (!TryComp(bodyToClone, out var humanoid)) return false; // whatever body was to be cloned, was not a humanoid @@ -188,7 +183,7 @@ namespace Content.Server.Cloning.Systems cloningCost = (int) Math.Round(cloningCost * EasyModeCloningCost); // biomass checks - var biomassAmount = podStorage.GetMaterialAmount("Biomass"); + var biomassAmount = _material.GetMaterialAmount(uid, "Biomass"); if (biomassAmount < cloningCost) { @@ -197,7 +192,7 @@ namespace Content.Server.Cloning.Systems return false; } - podStorage.RemoveMaterial("Biomass", cloningCost); + _material.TryChangeMaterialAmount(uid, "Biomass", -cloningCost); clonePod.UsedBiomass = cloningCost; // end of biomass checks diff --git a/Content.Server/Lathe/Components/LatheComponent.cs b/Content.Server/Lathe/Components/LatheComponent.cs deleted file mode 100644 index 8048e753ba..0000000000 --- a/Content.Server/Lathe/Components/LatheComponent.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; -using Robust.Server.GameObjects; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; -using Robust.Shared.Audio; - -namespace Content.Server.Lathe.Components -{ - [RegisterComponent] - public sealed class LatheComponent : SharedLatheComponent - { - /// - /// The lathe's construction queue - /// - [DataField("queue", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Queue { get; } = new(); - // TODO queue serializer. - - /// - /// How long the inserting animation will play - /// - [ViewVariables] - public float InsertionTime = 0.79f; // 0.01 off for animation timing - /// - /// Update accumulator for the insertion time - /// - [DataField("insertionAccumulator")] - public float InsertionAccumulator = 0f; - - /// - /// The sound that plays when the lathe is producing an item, if any - /// - [DataField("producingSound")] - public SoundSpecifier? ProducingSound; - - /// - /// The lathe's UI. - /// - [ViewVariables] public BoundUserInterface? UserInterface; - } -} diff --git a/Content.Server/Lathe/Components/LatheDatabaseComponent.cs b/Content.Server/Lathe/Components/LatheDatabaseComponent.cs deleted file mode 100644 index 7083d02333..0000000000 --- a/Content.Server/Lathe/Components/LatheDatabaseComponent.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; - -namespace Content.Server.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedLatheDatabaseComponent))] - public sealed class LatheDatabaseComponent : SharedLatheDatabaseComponent - { - /// - /// Whether new recipes can be added to this database or not. - /// - [ViewVariables] - [DataField("static")] - public bool Static { get; private set; } = false; - - public override ComponentState GetComponentState() - { - return new LatheDatabaseState(GetRecipeIdList()); - } - - public override void Clear() - { - if (Static) return; - base.Clear(); - Dirty(); - } - - public override void AddRecipe(LatheRecipePrototype recipe) - { - if (Static) return; - base.AddRecipe(recipe); - Dirty(); - } - - public override bool RemoveRecipe(LatheRecipePrototype recipe) - { - if (Static || !base.RemoveRecipe(recipe)) return false; - Dirty(); - return true; - } - } -} diff --git a/Content.Server/Lathe/Components/LatheInsertingComponent.cs b/Content.Server/Lathe/Components/LatheInsertingComponent.cs index f56ef45976..ef464ca853 100644 --- a/Content.Server/Lathe/Components/LatheInsertingComponent.cs +++ b/Content.Server/Lathe/Components/LatheInsertingComponent.cs @@ -2,7 +2,7 @@ namespace Content.Server.Lathe.Components { /// /// For EntityQuery to keep track of which lathes are inserting - /// + /// [RegisterComponent] public sealed class LatheInsertingComponent : Component { diff --git a/Content.Server/Lathe/Components/LatheProducingComponent.cs b/Content.Server/Lathe/Components/LatheProducingComponent.cs index 89a7c96e06..bd646e49da 100644 --- a/Content.Server/Lathe/Components/LatheProducingComponent.cs +++ b/Content.Server/Lathe/Components/LatheProducingComponent.cs @@ -1,24 +1,15 @@ -using Content.Shared.Research.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +namespace Content.Server.Lathe.Components; -namespace Content.Server.Lathe.Components +/// +/// For EntityQuery to keep track of which lathes are producing +/// +[RegisterComponent] +public sealed class LatheProducingComponent : Component { /// - /// For EntityQuery to keep track of which lathes are producing - /// - [RegisterComponent] - public sealed class LatheProducingComponent : Component - { - /// - /// The recipe the lathe is currently producing - /// - [DataField("recipe", required:true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? Recipe; - - /// - /// Remaining production time, in seconds. - /// - [DataField("timeRemaining", required: true)] - public float TimeRemaining; - } + /// How much production time has passed, in seconds. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float AccumulatedTime; } + diff --git a/Content.Server/Lathe/Components/MaterialStorageComponent.cs b/Content.Server/Lathe/Components/MaterialStorageComponent.cs deleted file mode 100644 index af6162c39c..0000000000 --- a/Content.Server/Lathe/Components/MaterialStorageComponent.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Content.Shared.Lathe; -using Content.Shared.Whitelist; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; -using Content.Shared.Materials; -using Robust.Shared.Audio; - -namespace Content.Server.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedMaterialStorageComponent))] - public sealed class MaterialStorageComponent : SharedMaterialStorageComponent - { - [ViewVariables] - protected override Dictionary Storage { get; set; } = new(); - - /// - /// How much material the storage can store in total. - /// - [ViewVariables] - public int StorageLimit => _storageLimit; - [DataField("StorageLimit")] - private int _storageLimit = -1; - - /// - /// Whitelist for specifying the kind of items that can be insert into this entity. - /// - [ViewVariables] - [DataField("whitelist")] - public EntityWhitelist? EntityWhitelist; - - /// - /// Whitelist generated on runtime for what specific materials can be inserted into this entity. - /// - [ViewVariables] - [DataField("materialWhiteList", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List MaterialWhiteList = new(); - - /// - /// The sound that plays when inserting an item into the storage - /// - [DataField("insertingSound")] - public SoundSpecifier? InsertingSound; - - 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); - } - - // forgive me I needed to write a crumb of e/c code to not go fucking insane i swear i will ecs this entire shitty fucking system one day - public int GetMaterialAmount(string id) - { - if (!Storage.TryGetValue(id, out var amount)) - return 0; - - return amount; - } - } -} diff --git a/Content.Server/Lathe/Components/ProtolatheDatabaseComponent.cs b/Content.Server/Lathe/Components/ProtolatheDatabaseComponent.cs deleted file mode 100644 index 260683b262..0000000000 --- a/Content.Server/Lathe/Components/ProtolatheDatabaseComponent.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Linq; -using Content.Server.Research.Components; -using Content.Shared.Lathe; -using Content.Shared.Research.Prototypes; -using Robust.Shared.Prototypes; - -namespace Content.Server.Lathe.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedLatheDatabaseComponent))] - public sealed class ProtolatheDatabaseComponent : SharedProtolatheDatabaseComponent - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - public override ComponentState GetComponentState() - { - return new ProtolatheDatabaseState(GetRecipeIdList()); - } - - /// - /// Adds unlocked recipes from technologies to the database. - /// - public void Sync() - { - if (!IoCManager.Resolve().TryGetComponent(Owner, out TechnologyDatabaseComponent? database)) return; - - foreach (var technology in database.Technologies) - { - foreach (var id in technology.UnlockedRecipes) - { - var recipe = (LatheRecipePrototype) _prototypeManager.Index(typeof(LatheRecipePrototype), id); - UnlockRecipe(recipe); - } - } - - Dirty(); - } - - /// - /// Unlocks a recipe but only if it's one of the allowed recipes on this protolathe. - /// - /// The recipe - /// Whether it could add it or not. - public bool UnlockRecipe(LatheRecipePrototype recipe) - { - if (!ProtolatheRecipes.Contains(recipe)) return false; - - AddRecipe(recipe); - - return true; - } - } -} diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 2e7a827644..7a9f9d4f3e 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -1,44 +1,49 @@ +using System.Diagnostics.CodeAnalysis; using Content.Server.Lathe.Components; using Content.Shared.Lathe; using Content.Shared.Materials; using Content.Shared.Research.Prototypes; using Content.Server.Research.Components; -using Content.Shared.Interaction; -using Content.Server.Materials; -using Content.Server.Popups; -using Content.Server.Power.EntitySystems; using Content.Server.Research; -using Content.Server.Stack; using Content.Shared.Research.Components; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; -using Robust.Shared.Player; using JetBrains.Annotations; using System.Linq; -using Content.Server.Cargo.Systems; +using Content.Server.Materials; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.UserInterface; using Robust.Server.Player; namespace Content.Server.Lathe { [UsedImplicitly] - public sealed class LatheSystem : EntitySystem + public sealed class LatheSystem : SharedLatheSystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSys = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UserInterfaceSystem _uiSys = default!; [Dependency] private readonly ResearchSystem _researchSys = default!; + [Dependency] private readonly MaterialStorageSystem _materialStorage = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnMaterialEntityInserted); + SubscribeLocalEvent(OnGetWhitelist); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnLatheQueueRecipeMessage); SubscribeLocalEvent(OnLatheSyncRequestMessage); SubscribeLocalEvent(OnLatheServerSelectionMessage); - SubscribeLocalEvent(OnPowerChanged); + + SubscribeLocalEvent((u,c,_) => UpdateUserInterfaceState(u,c)); + SubscribeLocalEvent((u,c,_) => UpdateUserInterfaceState(u,c)); + + SubscribeLocalEvent(OnGetRecipes); SubscribeLocalEvent(OnLatheServerSyncMessage); } @@ -55,217 +60,167 @@ namespace Content.Server.Lathe RemCompDeferred(comp.Owner, comp); } - foreach (var comp in EntityQuery()) + foreach (var (comp, lathe) in EntityQuery()) { - comp.TimeRemaining -= frameTime; + if (lathe.CurrentRecipe == null) + continue; - if (comp.TimeRemaining <= 0) - FinishProducing(comp.Owner, comp); + comp.AccumulatedTime += frameTime; + + if (comp.AccumulatedTime >= (float) lathe.CurrentRecipe.CompleteTime.TotalSeconds) + FinishProducing(comp.Owner, lathe); } } + private void OnGetWhitelist(EntityUid uid, LatheComponent component, GetMaterialWhitelistEvent args) + { + if (args.Storage != uid) + return; + var materialWhitelist = new List(); + var recipes = GetAllBaseRecipes(component); + foreach (var id in recipes) + { + if (!_proto.TryIndex(id, out var proto)) + continue; + foreach (var (mat, _) in proto.RequiredMaterials) + { + if (!materialWhitelist.Contains(mat)) + { + materialWhitelist.Add(mat); + } + } + } + + var combined = args.Whitelist.Union(materialWhitelist).ToList(); + args.Whitelist = combined; + } + + [PublicAPI] + public bool TryGetAvailableRecipes(EntityUid uid, [NotNullWhen(true)] out List? recipes, LatheComponent? component = null) + { + recipes = null; + if (!Resolve(uid, ref component)) + return false; + recipes = GetAvailableRecipes(component); + return true; + } + + public List GetAvailableRecipes(LatheComponent component) + { + var ev = new LatheGetRecipesEvent(component.Owner) + { + Recipes = component.StaticRecipes + }; + RaiseLocalEvent(component.Owner, ev); + return ev.Recipes; + } + + public List GetAllBaseRecipes(LatheComponent component) + { + return component.DynamicRecipes == null + ? component.StaticRecipes + : component.StaticRecipes.Union(component.DynamicRecipes).ToList(); + } + + public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, LatheComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + + if (!CanProduce(uid, recipe, 1, component)) + return false; + + foreach (var (mat, amount) in recipe.RequiredMaterials) + { + _materialStorage.TryChangeMaterialAmount(uid, mat, -amount); + } + component.Queue.Add(recipe); + + return true; + } + + public bool TryStartProducing(EntityUid uid, LatheComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + if (component.CurrentRecipe != null || component.Queue.Count <= 0 || !this.IsPowered(uid, EntityManager)) + return false; + + var recipe = component.Queue.First(); + component.Queue.RemoveAt(0); + + EnsureComp(uid); + component.CurrentRecipe = recipe; + + _audio.PlayPvs(component.ProducingSound, component.Owner); + UpdateRunningAppearance(uid, true); + UpdateUserInterfaceState(uid, component); + return true; + } + + public void FinishProducing(EntityUid uid, LatheComponent? comp = null, LatheProducingComponent? prodComp = null) + { + if (!Resolve(uid, ref comp, ref prodComp, false)) + return; + + if (comp.CurrentRecipe != null) + Spawn(comp.CurrentRecipe.Result, Transform(uid).Coordinates); + comp.CurrentRecipe = null; + prodComp.AccumulatedTime = 0; + + if (!TryStartProducing(uid, comp)) + { + RemCompDeferred(prodComp.Owner, prodComp); + UpdateUserInterfaceState(uid, comp); + UpdateRunningAppearance(uid, false); + } + } + + public void UpdateUserInterfaceState(EntityUid uid, LatheComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var ui = _uiSys.GetUi(uid, LatheUiKey.Key); + var producing = component.CurrentRecipe ?? component.Queue.FirstOrDefault(); + + var state = new LatheUpdateState(GetAvailableRecipes(component), component.Queue, producing); + _uiSys.SetUiState(ui, state); + } + + private void OnGetRecipes(EntityUid uid, TechnologyDatabaseComponent component, LatheGetRecipesEvent args) + { + if (uid != args.Lathe || !TryComp(uid, out var latheComponent) || latheComponent.DynamicRecipes == null) + return; + + //gets all of the techs that are unlocked and also in the DynamicRecipes list + var allTechs = (from tech in component.Technologies + from recipe in tech.UnlockedRecipes + where latheComponent.DynamicRecipes.Contains(recipe) + select recipe).ToList(); + + args.Recipes = args.Recipes.Union(allTechs).ToList(); + } + /// /// Initialize the UI and appearance. /// Appearance requires initialization or the layers break /// - private void OnComponentInit(EntityUid uid, LatheComponent component, ComponentInit args) + private void OnMapInit(EntityUid uid, LatheComponent component, MapInitEvent args) { - if (TryComp(uid, out var appearance)) - { - appearance.SetData(LatheVisuals.IsInserting, false); - appearance.SetData(LatheVisuals.IsRunning, false); - } + _appearance.SetData(uid, LatheVisuals.IsInserting, false); + _appearance.SetData(uid, LatheVisuals.IsRunning, false); - //Fix this awful shit once Lathes get ECS'd. - List? recipes = null; - if (TryComp(uid, out var database)) - recipes = database.ProtolatheRecipes.ToList(); - else if (TryComp(uid, out var database2)) - recipes = database2._recipes; - - if (recipes == null) - return; - - if (!TryComp(uid, out var storage)) - return; - - foreach (var recipe in recipes) - { - foreach (var mat in recipe.RequiredMaterials) - { - if (!storage.MaterialWhiteList.Contains(mat.Key)) - storage.MaterialWhiteList.Add(mat.Key); - } - } + _materialStorage.UpdateMaterialWhitelist(uid); } - private void OnInteractUsing(EntityUid uid, MaterialStorageComponent component, InteractUsingEvent args) + private void OnMaterialEntityInserted(EntityUid uid, LatheComponent component, MaterialEntityInsertedEvent args) { - if (args.Handled) - return; - - if (!TryComp(uid, out var storage) - || !TryComp(args.Used, out var material) - || storage.EntityWhitelist?.IsValid(args.Used) == false) - return; - - args.Handled = true; - - var matUsed = false; - foreach (var mat in material.Materials) - if (storage.MaterialWhiteList.Contains(mat.ID)) - matUsed = true; - - if (!matUsed) - { - _popupSystem.PopupEntity(Loc.GetString("lathe-popup-material-not-used"), uid, Filter.Pvs(uid)); - return; - } - - var multiplier = 1; - - if (TryComp(args.Used, out var stack)) - multiplier = stack.Count; - - var totalAmount = 0; - - // Check if it can insert all materials. - foreach (var (mat, vol) in material._materials) - { - if (!storage.CanInsertMaterial(mat, - vol * multiplier)) return; - totalAmount += vol * multiplier; - } - - // Check if it can take ALL of the material's volume. - if (storage.StorageLimit > 0 && !storage.CanTakeAmount(totalAmount)) - return; - - var lastMat = string.Empty; - foreach (var (mat, vol) in material._materials) - { - storage.InsertMaterial(mat, vol * multiplier); - lastMat = mat; - } - - EntityManager.QueueDeleteEntity(args.Used); - - // Play a sound when inserting, if any - if (component.InsertingSound != null) - _audioSys.PlayPvs(component.InsertingSound, uid); - - _popupSystem.PopupEntity(Loc.GetString("machine-insert-item", ("machine", uid), - ("item", args.Used)), uid, Filter.Entities(args.User)); - - // TODO: You can probably split this part off of lathe component too - if (!TryComp(uid, out var lathe)) - return; - + var lastMat = args.Materials.Keys.Last(); // We need the prototype to get the color - _prototypeManager.TryIndex(lastMat, out MaterialPrototype? matProto); - - EntityManager.QueueDeleteEntity(args.Used); - - EnsureComp(uid).TimeRemaining = lathe.InsertionTime; - - _popupSystem.PopupEntity(Loc.GetString("machine-insert-item", ("machine", uid), - ("item", args.Used)), uid, Filter.Entities(args.User)); - - if (matProto != null) - { - UpdateInsertingAppearance(uid, true, matProto.Color); - } - UpdateInsertingAppearance(uid, true); - } - - - private void OnPowerChanged(EntityUid uid, LatheComponent component, PowerChangedEvent args) - { - //if the power state changes, try to produce. - //aka, if you went from unpowered --> powered, resume lathe queue. - TryStartProducing(uid, component: component); - } - - /// - /// This handles the checks to start producing an item, and - /// starts up the sound and visuals - /// - private bool TryStartProducing(EntityUid uid, LatheProducingComponent? prodComp = null, LatheComponent? component = null) - { - if (!Resolve(uid, ref component) || component.Queue.Count == 0) - return false; - - if (!this.IsPowered(uid, EntityManager)) - return false; - - var recipeId = component.Queue[0]; - - if (!_prototypeManager.TryIndex(recipeId, out var recipe)) - { - // recipie does not exist. Remove and try produce the next item. - component.Queue.RemoveAt(0); - return TryStartProducing(uid, prodComp, component); - } - - if (!component.CanProduce(recipe) || !TryComp(uid, out MaterialStorageComponent? storage)) - { - component.Queue.RemoveAt(0); - return false; - } - - prodComp ??= EnsureComp(uid); - - // Do nothing if the lathe is already producing something. - if (prodComp.Recipe != null) - return false; - - component.Queue.RemoveAt(0); - prodComp.Recipe = recipeId; - prodComp.TimeRemaining = (float)recipe.CompleteTime.TotalSeconds; - - foreach (var (material, amount) in recipe.RequiredMaterials) - { - // This should always return true, otherwise CanProduce fucked up. - // TODO just remove materials when first queuing, to avoid queuing more items than can actually be produced. - storage.RemoveMaterial(material, amount); - } - - // Again, this should really just be a bui state instead of two separate messages. - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheProducingRecipeMessage(recipe.ID)); - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheFullQueueMessage(component.Queue)); - - if (component.ProducingSound != null) - _audioSys.PlayPvs(component.ProducingSound, component.Owner); - - UpdateRunningAppearance(uid, true); - return true; - } - - /// - /// If we were able to produce the recipe, - /// spawn it and cleanup. If we weren't, just do cleanup. - /// - private void FinishProducing(EntityUid uid, LatheProducingComponent prodComp) - { - if (prodComp.Recipe == null || !_prototypeManager.TryIndex(prodComp.Recipe, out var recipe)) - { - RemCompDeferred(prodComp.Owner, prodComp); - UpdateRunningAppearance(uid, false); - return; - } - - Spawn(recipe.Result, Transform(uid).Coordinates); - prodComp.Recipe = null; - - // TODO this should probably just be a BUI state, not a special message. - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheStoppedProducingRecipeMessage()); - - // Continue to next in queue if there are items left - if (TryStartProducing(uid, prodComp)) - return; - - RemComp(prodComp.Owner, prodComp); - UpdateRunningAppearance(uid, false); + _proto.TryIndex(lastMat, out MaterialPrototype? matProto); + EnsureComp(uid).TimeRemaining = component.InsertionTime; + UpdateInsertingAppearance(uid, true, matProto?.Color); } /// @@ -274,10 +229,7 @@ namespace Content.Server.Lathe /// private void UpdateRunningAppearance(EntityUid uid, bool isRunning) { - if (!TryComp(uid, out var appearance)) - return; - - appearance.SetData(LatheVisuals.IsRunning, isRunning); + _appearance.SetData(uid, LatheVisuals.IsRunning, isRunning); } /// @@ -286,56 +238,62 @@ namespace Content.Server.Lathe /// private void UpdateInsertingAppearance(EntityUid uid, bool isInserting, Color? color = null) { - if (!TryComp(uid, out var appearance)) - return; - - appearance.SetData(LatheVisuals.IsInserting, isInserting); + _appearance.SetData(uid, LatheVisuals.IsInserting, isInserting); if (color != null) - appearance.SetData(LatheVisuals.InsertingColor, color); + _appearance.SetData(uid, LatheVisuals.InsertingColor, color); + } + + private void OnPowerChanged(EntityUid uid, LatheComponent component, PowerChangedEvent args) + { + if (!args.Powered) + { + RemComp(uid); + UpdateRunningAppearance(uid, false); + } + else if (component.CurrentRecipe != null) + { + EnsureComp(uid); + TryStartProducing(uid, component); + } + } + + protected override bool HasRecipe(EntityUid uid, LatheRecipePrototype recipe, LatheComponent component) + { + return GetAvailableRecipes(component).Contains(recipe.ID); } #region UI Messages private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, LatheQueueRecipeMessage args) { - if (_prototypeManager.TryIndex(args.ID, out LatheRecipePrototype? recipe)) + if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe)) { for (var i = 0; i < args.Quantity; i++) { - // TODO check required materials exist and make materials unavailable. - component.Queue.Add(recipe.ID); + TryAddToQueue(uid, recipe, component); } - - // Again: TODO this should be handled by BUI states - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheFullQueueMessage(component.Queue)); } - - TryStartProducing(component.Owner, null, component); + TryStartProducing(uid, component); + UpdateUserInterfaceState(uid, component); } private void OnLatheSyncRequestMessage(EntityUid uid, LatheComponent component, LatheSyncRequestMessage args) { - if (!HasComp(uid)) return; - - // Again: TODO BUI states. Why TF was this was this ever two separate messages!?!? - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheFullQueueMessage(component.Queue)); - if (TryComp(uid, out LatheProducingComponent? prodComp) && prodComp.Recipe != null) - _uiSys.TrySendUiMessage(uid, LatheUiKey.Key, new LatheProducingRecipeMessage(prodComp.Recipe)); + UpdateUserInterfaceState(uid, component); } private void OnLatheServerSelectionMessage(EntityUid uid, LatheComponent component, LatheServerSelectionMessage args) { - // TODO W.. b.. why? - // the client can just open the ui itself. why tf is it asking the server to open it for it. - _uiSys.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session); + // TODO: one day, when you can open BUIs clientside, do that. Until then, picture Electro seething. + if (component.DynamicRecipes != null) + _uiSys.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session); } private void OnLatheServerSyncMessage(EntityUid uid, TechnologyDatabaseComponent component, LatheServerSyncMessage args) { + Logger.Debug("OnLatheServerSyncMessage"); _researchSys.SyncWithServer(component); - - if (TryComp(uid, out ProtolatheDatabaseComponent? protoDatabase)) - protoDatabase.Sync(); + UpdateUserInterfaceState(uid); } #endregion diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs new file mode 100644 index 0000000000..8487995278 --- /dev/null +++ b/Content.Server/Materials/MaterialStorageSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Materials; +using Content.Shared.Popups; +using Robust.Shared.Player; + +namespace Content.Server.Materials; + +/// +/// This handles +/// +public sealed class MaterialStorageSystem : SharedMaterialStorageSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + protected override void OnFinishInsertMaterialEntity(EntityUid toInsert, MaterialStorageComponent component) + { + _audio.PlayPvs(component.InsertingSound, component.Owner); + _popup.PopupEntity(Loc.GetString("machine-insert-item", ("machine", component.Owner), + ("item", toInsert)), component.Owner, Filter.Pvs(component.Owner)); + + QueueDel(toInsert); + } +} diff --git a/Content.Shared/Lathe/LatheComponent.cs b/Content.Shared/Lathe/LatheComponent.cs new file mode 100644 index 0000000000..0b070dc0b8 --- /dev/null +++ b/Content.Shared/Lathe/LatheComponent.cs @@ -0,0 +1,73 @@ +using Content.Shared.Research.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Lathe +{ + [RegisterComponent, NetworkedComponent] + public sealed class LatheComponent : Component + { + /// + /// All of the recipes that the lathe has by default + /// + [DataField("staticRecipes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public readonly List StaticRecipes = new(); + + /// + /// All of the recipes that the lathe is capable of researching + /// + [DataField("dynamicRecipes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public readonly List? DynamicRecipes; + + /// + /// The lathe's construction queue + /// + [DataField("queue")] + public List Queue = new(); + + /// + /// How long the inserting animation will play + /// + [DataField("insertionTime")] + public float InsertionTime = 0.79f; // 0.01 off for animation timing + + /// + /// The sound that plays when the lathe is producing an item, if any + /// + [DataField("producingSound")] + public SoundSpecifier? ProducingSound; + + #region Visualizer info + [DataField("idleState", required: true)] + public string IdleState = default!; + + [DataField("runningState", required: true)] + public string RunningState = default!; + + [ViewVariables] + [DataField("ignoreColor")] + public bool IgnoreColor; + #endregion + + /// + /// The recipe the lathe is currently producing + /// + [ViewVariables] + public LatheRecipePrototype? CurrentRecipe; + + + } + + public sealed class LatheGetRecipesEvent : EntityEventArgs + { + public readonly EntityUid Lathe; + + public List Recipes = new(); + + public LatheGetRecipesEvent(EntityUid lathe) + { + Lathe = lathe; + } + } +} diff --git a/Content.Shared/Lathe/LatheMessages.cs b/Content.Shared/Lathe/LatheMessages.cs index e23bea0ca7..09624f82f8 100644 --- a/Content.Shared/Lathe/LatheMessages.cs +++ b/Content.Shared/Lathe/LatheMessages.cs @@ -1,94 +1,60 @@ +using Content.Shared.Research.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Lathe; - /// - /// Sent to the server to sync material storage and the recipe queue. - /// - [Serializable, NetSerializable] - public sealed class LatheSyncRequestMessage : BoundUserInterfaceMessage - { - public LatheSyncRequestMessage() - { - } - } +[Serializable, NetSerializable] +public sealed class LatheUpdateState : BoundUserInterfaceState +{ + public List Recipes; - /// - /// Sent to the server to sync the lathe's technology database with the research server. - /// - [Serializable, NetSerializable] - public sealed class LatheServerSyncMessage : BoundUserInterfaceMessage - { - public LatheServerSyncMessage() - { - } - } + public List Queue; - /// - /// Sent to the server to open the ResearchClient UI. - /// - [Serializable, NetSerializable] - public sealed class LatheServerSelectionMessage : BoundUserInterfaceMessage - { - public LatheServerSelectionMessage() - { - } - } + public LatheRecipePrototype? CurrentlyProducing; - /// - /// Sent to the client when the lathe is producing a recipe. - /// - [Serializable, NetSerializable] - public sealed class LatheProducingRecipeMessage : BoundUserInterfaceMessage - { - public readonly string ID; - public LatheProducingRecipeMessage(string id) - { - ID = id; - } - } + public LatheUpdateState(List recipes, List queue, LatheRecipePrototype? currentlyProducing = null) + { + Recipes = recipes; + Queue = queue; + CurrentlyProducing = currentlyProducing; + } +} - /// - /// Sent to the client when the lathe stopped/finished producing a recipe. - /// - [Serializable, NetSerializable] - public sealed class LatheStoppedProducingRecipeMessage : BoundUserInterfaceMessage - { - public LatheStoppedProducingRecipeMessage() - { - } - } +/// +/// Sent to the server to sync material storage and the recipe queue. +/// +[Serializable, NetSerializable] +public sealed class LatheSyncRequestMessage : BoundUserInterfaceMessage { } - /// - /// Sent to the client to let it know about the recipe queue. - /// - [Serializable, NetSerializable] - public sealed class LatheFullQueueMessage : BoundUserInterfaceMessage - { - public readonly List Recipes; - public LatheFullQueueMessage(List recipes) - { - Recipes = recipes; - } - } +/// +/// Sent to the server to sync the lathe's technology database with the research server. +/// +[Serializable, NetSerializable] +public sealed class LatheServerSyncMessage : BoundUserInterfaceMessage { } - /// - /// Sent to the server when a client queues a new recipe. - /// - [Serializable, NetSerializable] - public sealed class LatheQueueRecipeMessage : BoundUserInterfaceMessage - { - public readonly string ID; - public readonly int Quantity; - public LatheQueueRecipeMessage(string id, int quantity) - { - ID = id; - Quantity = quantity; - } - } +/// +/// Sent to the server to open the ResearchClient UI. +/// +[Serializable, NetSerializable] +public sealed class LatheServerSelectionMessage : BoundUserInterfaceMessage { } - [NetSerializable, Serializable] - public enum LatheUiKey - { - Key, - } +/// +/// Sent to the server when a client queues a new recipe. +/// +[Serializable, NetSerializable] +public sealed 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/Lathe/LatheVisuals.cs b/Content.Shared/Lathe/LatheVisuals.cs index 7ae780a592..a05455b6a9 100644 --- a/Content.Shared/Lathe/LatheVisuals.cs +++ b/Content.Shared/Lathe/LatheVisuals.cs @@ -2,12 +2,12 @@ using Robust.Shared.Serialization; namespace Content.Shared.Lathe { - [Serializable, NetSerializable] /// /// Stores bools for if the machine is on /// and if it's currently running and/or inserting. /// Used for the visualizer /// + [Serializable, NetSerializable] public enum LatheVisuals : byte { IsRunning, diff --git a/Content.Shared/Lathe/SharedLatheComponent.cs b/Content.Shared/Lathe/SharedLatheComponent.cs deleted file mode 100644 index 217dd67911..0000000000 --- a/Content.Shared/Lathe/SharedLatheComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.Research.Prototypes; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Lathe -{ - [NetworkedComponent()] - [Virtual] - public class SharedLatheComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; - public bool CanProduce(LatheRecipePrototype recipe, int quantity = 1) - { - if (!_entMan.TryGetComponent(Owner, out SharedMaterialStorageComponent? storage) - || !_entMan.TryGetComponent(Owner, 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); - } - } -} diff --git a/Content.Shared/Lathe/SharedLatheDatabaseComponent.cs b/Content.Shared/Lathe/SharedLatheDatabaseComponent.cs deleted file mode 100644 index 65a77d178a..0000000000 --- a/Content.Shared/Lathe/SharedLatheDatabaseComponent.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Collections; -using Content.Shared.Research.Prototypes; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Lathe -{ - [NetworkedComponent()] - public abstract class SharedLatheDatabaseComponent : Component, IEnumerable, ISerializationHooks - { - [DataField("recipes", customTypeSerializer: typeof(PrototypeIdListSerializer))] private List _recipeIds = new(); - public readonly List _recipes = new(); - - void ISerializationHooks.BeforeSerialization() - { - var list = new List(); - - foreach (var recipe in _recipes) - { - list.Add(recipe.ID); - } - - _recipeIds = list; - } - - void ISerializationHooks.AfterDeserialization() - { - var prototypeManager = IoCManager.Resolve(); - - foreach (var id in _recipeIds) - { - if (prototypeManager.TryIndex(id, out LatheRecipePrototype? recipe)) - { - _recipes.Add(recipe); - } - } - } - - /// - /// 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) - { - if (!Contains(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 List GetRecipeIdList() - { - var list = new List(); - - foreach (var recipe in this) - { - list.Add(recipe.ID); - } - - return list; - } - - public IEnumerator GetEnumerator() - { - return _recipes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - [NetSerializable, Serializable] - public sealed class LatheDatabaseState : ComponentState - { - public readonly List Recipes; - public LatheDatabaseState(List recipes) - { - Recipes = recipes; - } - } -} diff --git a/Content.Shared/Lathe/SharedLatheSystem.cs b/Content.Shared/Lathe/SharedLatheSystem.cs new file mode 100644 index 0000000000..3814299218 --- /dev/null +++ b/Content.Shared/Lathe/SharedLatheSystem.cs @@ -0,0 +1,38 @@ +using Content.Shared.Materials; +using Content.Shared.Research.Prototypes; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Lathe; + +/// +/// This handles... +/// +public abstract class SharedLatheSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!; + + [PublicAPI] + public bool CanProduce(EntityUid uid, string recipe, int amount = 1, LatheComponent? component = null) + { + return _proto.TryIndex(recipe, out var proto) && CanProduce(uid, proto, amount, component); + } + + public bool CanProduce(EntityUid uid, LatheRecipePrototype recipe, int amount = 1, LatheComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + if (!HasRecipe(uid, recipe, component)) + return false; + + foreach (var (material, needed) in recipe.RequiredMaterials) + { + if (_materialStorage.GetMaterialAmount(component.Owner, material) < (amount * needed)) + return false; + } + return true; + } + + protected abstract bool HasRecipe(EntityUid uid, LatheRecipePrototype recipe, LatheComponent component); +} diff --git a/Content.Shared/Lathe/SharedMaterialStorageComponent.cs b/Content.Shared/Lathe/SharedMaterialStorageComponent.cs deleted file mode 100644 index da7fd191c2..0000000000 --- a/Content.Shared/Lathe/SharedMaterialStorageComponent.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections; -using Content.Shared.Materials; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Lathe -{ - [NetworkedComponent()] - public abstract class SharedMaterialStorageComponent : Component, IEnumerable> - { - [ViewVariables] - protected virtual Dictionary Storage { get; set; } = new(); - - public int this[string id] - { - get - { - if (!Storage.ContainsKey(id)) - return 0; - return Storage[id]; - } - } - - public int this[MaterialPrototype 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 sealed class MaterialStorageState : ComponentState - { - public readonly Dictionary Storage; - public MaterialStorageState(Dictionary storage) - { - Storage = storage; - } - } -} diff --git a/Content.Shared/Lathe/SharedProtolatheDatabaseComponent.cs b/Content.Shared/Lathe/SharedProtolatheDatabaseComponent.cs deleted file mode 100644 index 022eea1800..0000000000 --- a/Content.Shared/Lathe/SharedProtolatheDatabaseComponent.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Shared.Research.Prototypes; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Lathe -{ - [ComponentReference(typeof(SharedLatheDatabaseComponent))] - [NetworkedComponent()] - public abstract class SharedProtolatheDatabaseComponent : SharedLatheDatabaseComponent, ISerializationHooks - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - [DataField("protolatherecipes", customTypeSerializer:typeof(PrototypeIdListSerializer))] - private List _recipeIds = new(); - - /// - /// A full list of recipes this protolathe can print. - /// - public IEnumerable ProtolatheRecipes - { - get - { - foreach (var id in _recipeIds) - { - yield return _prototypeManager.Index(id); - } - } - } - } - - [NetSerializable, Serializable] - public sealed class ProtolatheDatabaseState : ComponentState - { - public readonly List Recipes; - public ProtolatheDatabaseState(List recipes) - { - Recipes = recipes; - } - } -} diff --git a/Content.Server/Materials/MaterialComponent.cs b/Content.Shared/Materials/MaterialComponent.cs similarity index 93% rename from Content.Server/Materials/MaterialComponent.cs rename to Content.Shared/Materials/MaterialComponent.cs index 0f71902538..6adf7e02f8 100644 --- a/Content.Server/Materials/MaterialComponent.cs +++ b/Content.Shared/Materials/MaterialComponent.cs @@ -1,15 +1,15 @@ using System.Linq; -using Content.Shared.Materials; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -namespace Content.Server.Materials +namespace Content.Shared.Materials { /// /// Component to store data such as "this object is made out of steel". /// This is not a storage system for say smelteries. /// - [RegisterComponent] + [RegisterComponent, NetworkedComponent] public sealed class MaterialComponent : Component { [ViewVariables] diff --git a/Content.Shared/Materials/MaterialStorageComponent.cs b/Content.Shared/Materials/MaterialStorageComponent.cs new file mode 100644 index 0000000000..0f955aaf57 --- /dev/null +++ b/Content.Shared/Materials/MaterialStorageComponent.cs @@ -0,0 +1,88 @@ +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Materials; + +[Access(typeof(SharedMaterialStorageSystem))] +[RegisterComponent, NetworkedComponent] +public sealed class MaterialStorageComponent : Component +{ + [ViewVariables] + public Dictionary Storage { get; set; } = new(); + + /// + /// How much material the storage can store in total. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("storageLimit")] + public int? StorageLimit; + + /// + /// Whitelist for specifying the kind of items that can be insert into this entity. + /// + [ViewVariables] + [DataField("whitelist")] + public EntityWhitelist? EntityWhitelist; + + /// + /// Whitelist generated on runtime for what specific materials can be inserted into this entity. + /// + [ViewVariables] + [DataField("materialWhiteList", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List? MaterialWhiteList; + + /// + /// The sound that plays when inserting an item into the storage + /// + [DataField("insertingSound")] + public SoundSpecifier? InsertingSound; +} + +/// +/// event raised on the materialStorage when a material entity is inserted into it. +/// +public readonly struct MaterialEntityInsertedEvent +{ + public readonly Dictionary Materials; + + public MaterialEntityInsertedEvent(Dictionary materials) + { + Materials = materials; + } +} + +/// +/// Event raised when a material amount is changed +/// +public readonly struct MaterialAmountChangedEvent +{ + +} + +public sealed class GetMaterialWhitelistEvent : EntityEventArgs +{ + public readonly EntityUid Storage; + + public List Whitelist = new(); + + public GetMaterialWhitelistEvent(EntityUid storage) + { + Storage = storage; + } +} + +[Serializable, NetSerializable] +public sealed class MaterialStorageComponentState : ComponentState +{ + public Dictionary Storage; + + public List? MaterialWhitelist; + + public MaterialStorageComponentState(Dictionary storage, List? materialWhitelist) + { + Storage = storage; + MaterialWhitelist = materialWhitelist; + } +} diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs new file mode 100644 index 0000000000..3a8770c785 --- /dev/null +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -0,0 +1,217 @@ +using System.Linq; +using Content.Shared.Interaction; +using Content.Shared.Stacks; +using JetBrains.Annotations; +using Robust.Shared.GameStates; + +namespace Content.Shared.Materials; + +/// +/// This handles storing materials and modifying their amounts +/// +/// +public abstract class SharedMaterialStorageSystem : EntitySystem +{ + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + private void OnGetState(EntityUid uid, MaterialStorageComponent component, ref ComponentGetState args) + { + args.State = new MaterialStorageComponentState(component.Storage, component.MaterialWhiteList); + } + + private void OnHandleState(EntityUid uid, MaterialStorageComponent component, ref ComponentHandleState args) + { + if (args.Current is not MaterialStorageComponentState state) + return; + + component.Storage = new Dictionary(state.Storage); + + if (state.MaterialWhitelist != null) + component.MaterialWhiteList = new List(state.MaterialWhitelist); + } + + /// + /// Gets the volume of a specified material contained in this storage. + /// + /// + /// + /// + /// The volume of the material + [PublicAPI] + public int GetMaterialAmount(EntityUid uid, MaterialPrototype material, MaterialStorageComponent? component = null) + { + return GetMaterialAmount(uid, material.ID, component); + } + + /// + /// Gets the volume of a specified material contained in this storage. + /// + /// + /// + /// + /// The volume of the material + public int GetMaterialAmount(EntityUid uid, string material, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return 0; //you have nothing + return !component.Storage.TryGetValue(material, out var amount) ? 0 : amount; + } + + /// + /// Gets the total volume of all materials in the storage. + /// + /// + /// + /// The volume of all materials in the storage + public int GetTotalMaterialAmount(EntityUid uid, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return 0; + return component.Storage.Values.Sum(); + } + + /// + /// Tests if a specific amount of volume will fit in the storage. + /// + /// + /// + /// + /// If the specified volume will fit + public bool CanTakeVolume(EntityUid uid, int volume, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + return component.StorageLimit == null || GetTotalMaterialAmount(uid, component) + volume <= component.StorageLimit; + } + + /// + /// Checks if the specified material can be changed by the specified volume. + /// + /// + /// + /// + /// + /// If the amount can be changed + public bool CanChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + return CanTakeVolume(uid, volume, component) && + (component.MaterialWhiteList == null || component.MaterialWhiteList.Contains(materialId)) && + (!component.Storage.TryGetValue(materialId, out var amount) || amount + volume >= 0); + } + + /// + /// Changes the amount of a specific material in the storage. + /// Still respects the filters in place. + /// + /// + /// + /// + /// + /// If it was successful + public bool TryChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component)) + return false; + if (!CanChangeMaterialAmount(uid, materialId, volume, component)) + return false; + if (!component.Storage.ContainsKey(materialId)) + component.Storage.Add(materialId, 0); + component.Storage[materialId] += volume; + + RaiseLocalEvent(uid, new MaterialAmountChangedEvent()); + Dirty(component); + return true; + } + + /// + /// Tries to insert an entity into the material storage. + /// + /// + /// + /// + /// If it was successful + public bool TryInsertMaterialEntity(EntityUid toInsert, EntityUid receiver, MaterialStorageComponent? component = null) + { + if (!Resolve(receiver, ref component)) + return false; + + if (!TryComp(toInsert, out var material)) + return false; + + if (component.EntityWhitelist?.IsValid(toInsert) == false) + return false; + + if (component.MaterialWhiteList != null) + { + var matUsed = false; + foreach (var mat in material.Materials) + { + if (component.MaterialWhiteList.Contains(mat.ID)) + matUsed = true; + } + + if (!matUsed) + return false; + } + + var multiplier = TryComp(toInsert, out var stackComponent) ? stackComponent.Count : 1; + + var totalVolume = 0; + foreach (var (mat, vol) in component.Storage) + { + if (!CanChangeMaterialAmount(receiver, mat, vol, component)) + return false; + totalVolume += vol * multiplier; + } + + if (!CanTakeVolume(receiver, totalVolume, component)) + return false; + + foreach (var (mat, vol) in material._materials) + { + TryChangeMaterialAmount(receiver, mat, vol * multiplier, component); + } + + OnFinishInsertMaterialEntity(toInsert, component); + RaiseLocalEvent(component.Owner, new MaterialEntityInsertedEvent(material._materials)); + return true; + } + + /// + /// Broadcasts an event that will collect a list of which materials + /// are allowed to be inserted into the materialStorage. + /// + /// + /// + public void UpdateMaterialWhitelist(EntityUid uid, MaterialStorageComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return; + var ev = new GetMaterialWhitelistEvent(uid); + RaiseLocalEvent(uid, ev); + component.MaterialWhiteList = ev.Whitelist; + } + + /// + /// This is done because of popup spam and not being able + /// to do entity deletion clientside. + /// + protected abstract void OnFinishInsertMaterialEntity(EntityUid toInsert, MaterialStorageComponent component); + + private void OnInteractUsing(EntityUid uid, MaterialStorageComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + args.Handled = TryInsertMaterialEntity(args.Used, uid, component); + } +} diff --git a/Resources/Locale/en-US/lathe/ui/lathe-menu.ftl b/Resources/Locale/en-US/lathe/ui/lathe-menu.ftl index acf8bebe10..2dd8d87e1d 100644 --- a/Resources/Locale/en-US/lathe/ui/lathe-menu.ftl +++ b/Resources/Locale/en-US/lathe/ui/lathe-menu.ftl @@ -5,3 +5,4 @@ lathe-menu-sync = Sync lathe-menu-search-designs = Search designs lathe-menu-search-filter = Filter lathe-menu-search-amount = Amount +lathe-menu-material-display = {$material} {$amount} cm³ \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 31ab178a29..77d26586e6 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -19,9 +19,6 @@ - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Appearance - - type: LatheVisuals - idleState: icon - runningState: building - type: WiresVisuals - type: Physics bodyType: Static @@ -56,9 +53,20 @@ - type: Wires BoardName: "Autolathe" LayoutId: Autolathe - - type: LatheDatabase - static: true - recipes: + - type: ActivatableUI + key: enum.LatheUiKey.Key + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + - key: enum.LatheUiKey.Key + type: LatheBoundUserInterface + - type: Transform + anchored: true + - type: Pullable + - type: Lathe + idleState: icon + runningState: building + staticRecipes: - Wirecutter - Screwdriver - Welder @@ -74,17 +82,6 @@ - TRayScanner - GasAnalyzer - UtilityBelt - - type: ActivatableUI - key: enum.LatheUiKey.Key - - type: ActivatableUIRequiresPower - - type: UserInterface - interfaces: - - key: enum.LatheUiKey.Key - type: LatheBoundUserInterface - - type: Transform - anchored: true - - type: Pullable - - type: Lathe - type: StaticPrice price: 800 @@ -109,9 +106,6 @@ - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Appearance - - type: LatheVisuals - idleState: icon - runningState: building - type: WiresVisuals - type: Physics bodyType: Static @@ -148,8 +142,22 @@ - Sheet - RawMaterial - Ingot - - type: ProtolatheDatabase - protolatherecipes: + - type: ActivatableUI + key: enum.LatheUiKey.Key #Yes only having 1 of them here doesn't break anything + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + - key: enum.LatheUiKey.Key + type: LatheBoundUserInterface + - key: enum.ResearchClientUiKey.Key + type: ResearchClientBoundUserInterface + - type: Transform + anchored: true + - type: Pullable + - type: Lathe + idleState: icon + runningState: building + dynamicRecipes: - LightTube - LightBulb - SheetSteel @@ -210,19 +218,6 @@ - LeftArmBorg - RightArmBorg - HolofanProjector - - type: ActivatableUI - key: enum.LatheUiKey.Key #Yes only having 1 of them here doesn't break anything - - type: ActivatableUIRequiresPower - - type: UserInterface - interfaces: - - key: enum.LatheUiKey.Key - type: LatheBoundUserInterface - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface - - type: Transform - anchored: true - - type: Pullable - - type: Lathe - type: entity parent: Protolathe @@ -241,8 +236,13 @@ map: ["enum.PowerDeviceVisualLayers.Powered"] - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: ProtolatheDatabase - protolatherecipes: + - type: Machine + board: CircuitImprinterMachineCircuitboard + - type: Lathe + producingSound: /Audio/Machines/circuitprinter.ogg + idleState: icon + runningState: building + dynamicRecipes: - SMESMachineCircuitboard - SubstationMachineCircuitboard - ThermomachineFreezerMachineCircuitBoard @@ -282,10 +282,6 @@ - WallmountSubstationElectronics - EmitterCircuitboard - GasRecyclerMachineCircuitboard - - type: Machine - board: CircuitImprinterMachineCircuitboard - - type: Lathe - producingSound: /Audio/Machines/circuitprinter.ogg - type: MaterialStorage whitelist: tags: @@ -312,12 +308,12 @@ map: ["enum.LatheVisualLayers.IsInserting"] - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: LatheVisuals + - type: Machine + board: SecurityTechFabCircuitboard + - type: Lathe idleState: icon runningState: icon - - type: LatheDatabase - static: true - recipes: + dynamicRecipes: - Flash - FlashPayload - Handcuffs @@ -335,9 +331,6 @@ - CartridgeCaselessRifleRubber - CartridgeLightRifleRubber - CartridgeRifleRubber - - type: Machine - board: SecurityTechFabCircuitboard - - type: Lathe - type: MaterialStorage whitelist: tags: @@ -365,11 +358,10 @@ map: ["enum.LatheVisualLayers.IsInserting"] - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: LatheVisuals + - type: Lathe idleState: icon - runningState: icon - - type: ProtolatheDatabase - protolatherecipes: + runningState: building + dynamicRecipes: - HandheldHealthAnalyzer - ClothingHandsGlovesLatex - ClothingHandsGlovesNitrile @@ -401,9 +393,20 @@ components: - type: Transform noRot: false - - type: LatheDatabase - static: true - recipes: + - type: Sprite + sprite: Structures/Machines/uniform_printer.rsi + netsync: false + snapCardinals: false + layers: + - state: icon + map: ["enum.LatheVisualLayers.IsRunning"] + - type: Machine + board: UniformPrinterMachineCircuitboard + - type: Lathe + producingSound: /Audio/Machines/uniformprinter.ogg + idleState: icon + runningState: building + staticRecipes: - ClothingUniformJumpsuitColorGrey - ClothingUniformJumpskirtColorGrey - ClothingUniformJumpsuitBartender @@ -468,17 +471,6 @@ - ClothingOuterWinterMusician - ClothingOuterWinterClown - ClothingOuterWinterMime - - type: Sprite - sprite: Structures/Machines/uniform_printer.rsi - netsync: false - snapCardinals: false - layers: - - state: icon - map: ["enum.LatheVisualLayers.IsRunning"] - - type: Machine - board: UniformPrinterMachineCircuitboard - - type: Lathe - producingSound: /Audio/Machines/uniformprinter.ogg - type: MaterialStorage whitelist: tags: @@ -492,8 +484,6 @@ name: ore processor description: It produces sheets and ingots using ores. components: - - type: LatheVisuals - ignoreColor: true - type: Sprite sprite: Structures/Machines/ore_processor.rsi netsync: false @@ -509,9 +499,13 @@ map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Machine board: OreProcessorMachineCircuitboard - - type: LatheDatabase - static: true - recipes: + - type: MaterialStorage + whitelist: + tags: + - Ore + - type: Lathe + ignoreColor: true + staticRecipes: - SheetSteel30 - SheetGlass30 - SheetRGlass30 @@ -521,8 +515,3 @@ - SheetUranium1 - IngotGold1 - IngotSilver1 - - type: MaterialStorage - whitelist: - tags: - - Ore - - type: Lathe