diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs index 3e04b184b4..ed2307b165 100644 --- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs +++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs @@ -1,8 +1,6 @@ using Content.Shared.Lathe; -using Content.Shared.Materials; using Content.Shared.Research.Components; using JetBrains.Annotations; -using Robust.Client.GameObjects; namespace Content.Client.Lathe.UI { @@ -33,11 +31,6 @@ namespace Content.Client.Lathe.UI SendMessage(new LatheQueueRecipeMessage(recipe, amount)); }; - _menu.OnEjectPressed += (material, sheetsToExtract) => - { - SendMessage(new EjectMaterialMessage(material, sheetsToExtract)); - }; - _menu.OpenCentered(); } @@ -51,7 +44,6 @@ namespace Content.Client.Lathe.UI if (_menu != null) _menu.Recipes = msg.Recipes; _menu?.PopulateRecipes(Owner); - _menu?.PopulateMaterials(Owner); _menu?.PopulateQueueList(msg.Queue); _menu?.SetQueueInfo(msg.CurrentlyProducing); break; @@ -64,7 +56,6 @@ namespace Content.Client.Lathe.UI if (!disposing) return; _menu?.Dispose(); - //thom _materialsEjectionMenu?.Dispose(); } } } diff --git a/Content.Client/Lathe/UI/LatheMaterialEjector.xaml.cs b/Content.Client/Lathe/UI/LatheMaterialEjector.xaml.cs deleted file mode 100644 index faa397a594..0000000000 --- a/Content.Client/Lathe/UI/LatheMaterialEjector.xaml.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Content.Client.Stylesheets; -using Content.Shared.Materials; -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; - -namespace Content.Client.Lathe.UI; - -/// -/// This widget is one row in the lathe eject menu. -/// -[GenerateTypedNameReferences] -public sealed partial class LatheMaterialEjector : PanelContainer -{ - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - public string Material; - public Action? OnEjectPressed; - public int VolumePerSheet; - - public LatheMaterialEjector(string material, Action? onEjectPressed, int volumePerSheet, int maxEjectableSheets) - { - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - - Material = material; - OnEjectPressed = onEjectPressed; - VolumePerSheet = volumePerSheet; - - PopulateButtons(maxEjectableSheets); - } - - public void PopulateButtons(int maxEjectableSheets) - { - int[] sheetsToEjectArray = { 1, 5, 10 }; - - for (var i = 0; i < sheetsToEjectArray.Length; i++) - { - var sheetsToEject = sheetsToEjectArray[i]; - - var styleClass = StyleBase.ButtonOpenBoth; - if (i == 0) - styleClass = StyleBase.ButtonOpenRight; - else if (i == sheetsToEjectArray.Length - 1) - styleClass = StyleBase.ButtonOpenLeft; - - var button = new Button - { - Name = $"{sheetsToEject}", - Access = AccessLevel.Public, - Text = Loc.GetString($"{sheetsToEject}"), - MinWidth = 45, - StyleClasses = { styleClass } - }; - - button.OnPressed += _ => - { - OnEjectPressed?.Invoke(Material, sheetsToEject); - }; - - button.Disabled = maxEjectableSheets < sheetsToEject; - - if (_prototypeManager.TryIndex(Material, out var proto)) - { - button.ToolTip = Loc.GetString("lathe-menu-tooltip-display", ("amount", sheetsToEject), ("material", Loc.GetString(proto.Name))); - } - - Content.AddChild(button); - } - } -} diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml index 539c08d652..1e60db68e2 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml +++ b/Content.Client/Lathe/UI/LatheMenu.xaml @@ -1,6 +1,7 @@ @@ -135,14 +136,7 @@ Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"> - - - + diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs index 71b45fc7dc..88fdcccbf0 100644 --- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs +++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Text; +using Content.Client.Materials; using Content.Shared.Lathe; using Content.Shared.Materials; using Content.Shared.Research.Prototypes; @@ -19,16 +20,12 @@ public sealed partial class LatheMenu : DefaultWindow [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly SpriteSystem _spriteSystem; private readonly LatheSystem _lathe; + private readonly MaterialStorageSystem _materialStorage; public event Action? OnServerListButtonPressed; public event Action? RecipeQueueAction; - public event Action? OnEjectPressed; - public List> Recipes = new(); - /// - /// Default volume for a sheet if the material's entity prototype has no material composition. - /// - private const int DEFAULT_SHEET_VOLUME = 100; + public List> Recipes = new(); public LatheMenu(LatheBoundUserInterface owner) { @@ -37,6 +34,7 @@ public sealed partial class LatheMenu : DefaultWindow _spriteSystem = _entityManager.System(); _lathe = _entityManager.System(); + _materialStorage = _entityManager.System(); Title = _entityManager.GetComponent(owner.Owner).EntityName; @@ -58,50 +56,10 @@ public sealed partial class LatheMenu : DefaultWindow ServerListButton.Visible = false; } } + + MaterialsList.SetOwner(owner.Owner); } - public void PopulateMaterials(EntityUid lathe) - { - if (!_entityManager.TryGetComponent(lathe, out var materials)) - return; - - MaterialsList.DisposeAllChildren(); - - foreach (var (materialId, volume) in materials.Storage) - { - if (volume <= 0) - continue; - - if (!_prototypeManager.TryIndex(materialId, out MaterialPrototype? material)) - continue; - - var sheetVolume = SheetVolume(material); - var sheets = (float) volume / sheetVolume; - var maxEjectableSheets = (int) MathF.Floor(sheets); - - var unit = Loc.GetString(material.Unit); - var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); - var name = Loc.GetString(material.Name); - var mat = Loc.GetString("lathe-menu-material-display", ("material", name), ("amount", amountText)); - - var row = new LatheMaterialEjector(materialId, OnEjectPressed, sheetVolume, maxEjectableSheets) - { - Icon = { Texture = _spriteSystem.Frame0(material.Icon) }, - ProductName = { Text = mat } - }; - - MaterialsList.AddChild(row); - } - - if (MaterialsList.ChildCount == 0) - { - var noMaterialsMsg = Loc.GetString("lathe-menu-no-materials-message"); - var noItemRow = new Label(); - noItemRow.Text = noMaterialsMsg; - noItemRow.Align = Label.AlignMode.Center; - MaterialsList.AddChild(noItemRow); - } - } /// /// Populates the list of all the recipes /// @@ -147,7 +105,7 @@ public sealed partial class LatheMenu : DefaultWindow sb.Append('\n'); var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier); - var sheetVolume = SheetVolume(proto); + var sheetVolume = _materialStorage.GetSheetVolume(proto); var unit = Loc.GetString(proto.Unit); // rounded in locale not here @@ -204,17 +162,4 @@ public sealed partial class LatheMenu : DefaultWindow : _spriteSystem.Frame0(recipe.Icon); NameLabel.Text = $"{recipe.Name}"; } - - private int SheetVolume(MaterialPrototype material) - { - if (material.StackEntity == null) - return DEFAULT_SHEET_VOLUME; - - var proto = _prototypeManager.Index(material.StackEntity); - - if (!proto.TryGetComponent(out var composition)) - return DEFAULT_SHEET_VOLUME; - - return composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == material.ID).Value; - } } diff --git a/Content.Client/Lathe/UI/LatheMaterialEjector.xaml b/Content.Client/Materials/UI/MaterialDisplay.xaml similarity index 67% rename from Content.Client/Lathe/UI/LatheMaterialEjector.xaml rename to Content.Client/Materials/UI/MaterialDisplay.xaml index 08c7da5302..653a3f0463 100644 --- a/Content.Client/Lathe/UI/LatheMaterialEjector.xaml +++ b/Content.Client/Materials/UI/MaterialDisplay.xaml @@ -1,8 +1,6 @@ - + - + + + diff --git a/Content.Client/Materials/UI/MaterialDisplay.xaml.cs b/Content.Client/Materials/UI/MaterialDisplay.xaml.cs new file mode 100644 index 0000000000..eaa16af658 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialDisplay.xaml.cs @@ -0,0 +1,110 @@ +using Content.Client.Stylesheets; +using Content.Shared.Materials; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Materials.UI; + +/// +/// This widget is one row in the material storage control. +/// +[GenerateTypedNameReferences] +public sealed partial class MaterialDisplay : PanelContainer +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + + private readonly MaterialStorageSystem _materialStorage; + + private readonly MaterialStorageUIController _materialUIController; + + private int _volume; + private readonly EntityUid _ent; + public readonly string Material; + private readonly bool _canEject; + + public MaterialDisplay(EntityUid ent, string material, int volume, bool canEject) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _materialStorage = _entityManager.System(); + _materialUIController = UserInterfaceManager.GetUIController(); + + var spriteSys = _entityManager.System(); + Icon.Texture = spriteSys.Frame0(_prototypeManager.Index(material).Icon); + + _ent = ent; + Material = material; + _canEject = canEject; + UpdateVolume(volume); + } + + public void UpdateVolume(int volume) + { + if (_volume == volume) + return; + + _volume = volume; + var matProto = _prototypeManager.Index(Material); + + var sheetVolume = _materialStorage.GetSheetVolume(matProto); + var sheets = (float) volume / sheetVolume; + var maxEjectableSheets = (int) MathF.Floor(sheets); + + var unit = Loc.GetString(matProto.Unit); + var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); + var name = Loc.GetString(matProto.Name); + var mat = Loc.GetString("lathe-menu-material-display", ("material", name), ("amount", amountText)); + ProductName.Text = mat; + + PopulateButtons(maxEjectableSheets); + } + + public void PopulateButtons(int maxEjectableSheets) + { + Content.Children.Clear(); + if (!_canEject) + return; + + int[] sheetsToEjectArray = { 1, 5, 10 }; + + for (var i = 0; i < sheetsToEjectArray.Length; i++) + { + var sheetsToEject = sheetsToEjectArray[i]; + + var styleClass = StyleBase.ButtonOpenBoth; + if (i == 0) + styleClass = StyleBase.ButtonOpenRight; + else if (i == sheetsToEjectArray.Length - 1) + styleClass = StyleBase.ButtonOpenLeft; + + var button = new Button + { + Name = $"{sheetsToEject}", + Access = AccessLevel.Public, + Text = Loc.GetString($"{sheetsToEject}"), + MinWidth = 45, + StyleClasses = { styleClass } + }; + + button.OnPressed += _ => + { + _materialUIController.SendLatheEjectMessage(_ent, Material, sheetsToEject); + }; + + button.Disabled = maxEjectableSheets < sheetsToEject; + + if (_prototypeManager.TryIndex(Material, out var proto)) + { + button.ToolTip = Loc.GetString("lathe-menu-tooltip-display", ("amount", sheetsToEject), ("material", Loc.GetString(proto.Name))); + } + + Content.AddChild(button); + } + } +} diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml b/Content.Client/Materials/UI/MaterialStorageControl.xaml new file mode 100644 index 0000000000..e504434649 --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml @@ -0,0 +1,7 @@ + + diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs new file mode 100644 index 0000000000..c95bd1957f --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs @@ -0,0 +1,92 @@ +using System.Linq; +using Content.Shared.Materials; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; + +namespace Content.Client.Materials.UI; + +/// +/// This widget is one row in the lathe eject menu. +/// +[GenerateTypedNameReferences] +public sealed partial class MaterialStorageControl : BoxContainer +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + + private EntityUid? _owner; + + private Dictionary _currentMaterials = new(); + + public MaterialStorageControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + } + + public void SetOwner(EntityUid owner) + { + _owner = owner; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + if (_owner == null) + return; + + if (!_entityManager.TryGetComponent(_owner, out var materialStorage)) + { + Dispose(); + return; + } + + var canEject = materialStorage.CanEjectStoredMaterials; + var mats = materialStorage.Storage.Select(pair => (pair.Key.Id, pair.Value)).ToDictionary(); + if (_currentMaterials.Equals(mats)) + return; + + var missing = new List(); + var extra = new List(); + foreach (var (mat, amount) in mats) + { + if (!_currentMaterials.ContainsKey(mat) || + _currentMaterials[mat] == 0 && _currentMaterials[mat] != amount) + missing.Add(mat); + } + foreach (var (mat, amount) in _currentMaterials) + { + if (!mats.ContainsKey(mat) || amount == 0) + extra.Add(mat); + } + + var children = new List(); + children.AddRange(Children.OfType()); + + foreach (var display in children) + { + var mat = display.Material; + + if (extra.Contains(mat)) + { + RemoveChild(display); + continue; + } + + if (!mats.TryGetValue(mat, out var newAmount)) + continue; + display.UpdateVolume(newAmount); + } + + foreach (var mat in missing) + { + var volume = mats[mat]; + AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject)); + } + + _currentMaterials = mats; + NoMatsLabel.Visible = ChildCount == 1; + } +} diff --git a/Content.Client/Materials/UI/MaterialStorageUIController.cs b/Content.Client/Materials/UI/MaterialStorageUIController.cs new file mode 100644 index 0000000000..943aa0a15a --- /dev/null +++ b/Content.Client/Materials/UI/MaterialStorageUIController.cs @@ -0,0 +1,12 @@ +using Content.Shared.Materials; +using Robust.Client.UserInterface.Controllers; + +namespace Content.Client.Materials.UI; + +public sealed class MaterialStorageUIController : UIController +{ + public void SendLatheEjectMessage(EntityUid uid, string material, int sheetsToEject) + { + EntityManager.RaisePredictiveEvent(new EjectMaterialMessage(EntityManager.GetNetEntity(uid), material, sheetsToEject)); + } +} diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs index 336a987505..25e409fd01 100644 --- a/Content.Server/Materials/MaterialStorageSystem.cs +++ b/Content.Server/Materials/MaterialStorageSystem.cs @@ -5,10 +5,10 @@ using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Server.Power.Components; using Content.Server.Stack; +using Content.Shared.ActionBlocker; using Content.Shared.Construction; using Content.Shared.Database; using JetBrains.Annotations; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -22,6 +22,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly StackSystem _stackSystem = default!; @@ -31,7 +32,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem base.Initialize(); SubscribeLocalEvent(OnDeconstructed); - SubscribeLocalEvent(OnEjectMessage); + SubscribeAllEvent(OnEjectMessage); } private void OnDeconstructed(EntityUid uid, MaterialStorageComponent component, MachineDeconstructedEvent args) @@ -45,9 +46,23 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem } } - private void OnEjectMessage(EntityUid uid, MaterialStorageComponent component, EjectMaterialMessage message) + private void OnEjectMessage(EjectMaterialMessage msg, EntitySessionEventArgs args) { - if (!component.CanEjectStoredMaterials || !_prototypeManager.TryIndex(message.Material, out var material)) + if (args.SenderSession.AttachedEntity is not { } player) + return; + + var uid = GetEntity(msg.Entity); + + if (!TryComp(uid, out var component)) + return; + + if (!Exists(uid)) + return; + + if (!_actionBlocker.CanInteract(player, uid)) + return; + + if (!component.CanEjectStoredMaterials || !_prototypeManager.TryIndex(msg.Material, out var material)) return; var volume = 0; @@ -57,13 +72,13 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem if (!_prototypeManager.Index(material.StackEntity).TryGetComponent(out var composition)) return; - var volumePerSheet = composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == message.Material).Value; - var sheetsToExtract = Math.Min(message.SheetsToExtract, _stackSystem.GetMaxCount(material.StackEntity)); + var volumePerSheet = composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == msg.Material).Value; + var sheetsToExtract = Math.Min(msg.SheetsToExtract, _stackSystem.GetMaxCount(material.StackEntity)); volume = sheetsToExtract * volumePerSheet; } - if (volume <= 0 || !TryChangeMaterialAmount(uid, message.Material, -volume)) + if (volume <= 0 || !TryChangeMaterialAmount(uid, msg.Material, -volume)) return; var mats = SpawnMultipleFromMaterial(volume, material, Transform(uid).Coordinates, out _); @@ -121,7 +136,7 @@ public sealed class MaterialStorageSystem : SharedMaterialStorageSystem overflowMaterial = 0; if (!_prototypeManager.TryIndex(material, out var stackType)) { - Logger.Error("Failed to index material prototype " + material); + Log.Error("Failed to index material prototype " + material); return new List(); } diff --git a/Content.Shared/Materials/MaterialStorageComponent.cs b/Content.Shared/Materials/MaterialStorageComponent.cs index 6762dfeadc..7d8dd5c749 100644 --- a/Content.Shared/Materials/MaterialStorageComponent.cs +++ b/Content.Shared/Materials/MaterialStorageComponent.cs @@ -105,13 +105,15 @@ public record struct GetMaterialWhitelistEvent(EntityUid Storage) /// Message sent to try and eject a material from a storage /// [Serializable, NetSerializable] -public sealed class EjectMaterialMessage : BoundUserInterfaceMessage +public sealed class EjectMaterialMessage : EntityEventArgs { + public NetEntity Entity; public string Material; public int SheetsToExtract; - public EjectMaterialMessage(string material, int sheetsToExtract) + public EjectMaterialMessage(NetEntity entity, string material, int sheetsToExtract) { + Entity = entity; Material = material; SheetsToExtract = sheetsToExtract; } diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index 9e6e000d62..a0cd7a9e45 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -18,6 +18,11 @@ public abstract class SharedMaterialStorageSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + /// + /// Default volume for a sheet if the material's entity prototype has no material composition. + /// + private const int DefaultSheetVolume = 100; + /// public override void Initialize() { @@ -249,4 +254,17 @@ public abstract class SharedMaterialStorageSystem : EntitySystem return; args.Handled = TryInsertMaterialEntity(args.User, args.Used, uid, component); } + + public int GetSheetVolume(MaterialPrototype material) + { + if (material.StackEntity == null) + return DefaultSheetVolume; + + var proto = _prototype.Index(material.StackEntity); + + if (!proto.TryGetComponent(out var composition)) + return DefaultSheetVolume; + + return composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == material.ID).Value; + } }