From e5ee1a1acdc5bb92a6b4022eb84011b82dc2dc6e Mon Sep 17 00:00:00 2001 From: C_Quarian Date: Sun, 13 Sep 2026 19:43:02 +0400 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=20=D0=92=D0=B5=D0=BD=D0=B4=D0=BE=D0=BC=D0=B0=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разобраться в щиткоде было больно, я не знаю ПОЧЕМУ UI берётся из загрузчика боеприпасов --- .../UI/VendingMachineItem.xaml | 26 -- .../UI/VendingMachineItem.xaml.cs | 19 -- .../UI/VendingMachineMenu.xaml | 54 ---- .../UI/VendingMachineMenu.xaml.cs | 256 ------------------ .../VendingMachineBoundUserInterface.cs | 33 +-- .../_Lua/AmmoLoader/UI/MarqueeLabel.cs | 25 ++ Content.Client/_Lua/Styles/LunaWindow.cs | 22 ++ Content.Client/_Lua/Styles/LunaWindowStyle.cs | 119 ++++++++ .../LuaVendingMachineWindow.xaml | 41 +++ .../LuaVendingMachineWindow.xaml.cs | 229 ++++++++++++++++ .../VendingMachines/UI/LuaVendingButton.xaml | 4 + .../UI/LuaVendingButton.xaml.cs | 27 ++ .../VendingMachines/UI/LuaVendingEntry.xaml | 15 + .../UI/LuaVendingEntry.xaml.cs | 32 +++ .../VendingMachines/UI/LuaVendingPanel.xaml | 18 ++ .../UI/LuaVendingPanel.xaml.cs | 53 ++++ Resources/Locale/en-US/_Lua/vending.ftl | 1 + Resources/Locale/ru-RU/_Lua/vending.ftl | 1 + 18 files changed, 597 insertions(+), 378 deletions(-) delete mode 100644 Content.Client/VendingMachines/UI/VendingMachineItem.xaml delete mode 100644 Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs delete mode 100644 Content.Client/VendingMachines/UI/VendingMachineMenu.xaml delete mode 100644 Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs create mode 100644 Content.Client/_Lua/Styles/LunaWindow.cs create mode 100644 Content.Client/_Lua/Styles/LunaWindowStyle.cs create mode 100644 Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml create mode 100644 Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml.cs create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml.cs create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml create mode 100644 Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml.cs create mode 100644 Resources/Locale/en-US/_Lua/vending.ftl create mode 100644 Resources/Locale/ru-RU/_Lua/vending.ftl diff --git a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml b/Content.Client/VendingMachines/UI/VendingMachineItem.xaml deleted file mode 100644 index 2ad19f7255f..00000000000 --- a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - diff --git a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs deleted file mode 100644 index a7212934fd8..00000000000 --- a/Content.Client/VendingMachines/UI/VendingMachineItem.xaml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; - -namespace Content.Client.VendingMachines.UI; - -[GenerateTypedNameReferences] -public sealed partial class VendingMachineItem : BoxContainer -{ - public VendingMachineItem(EntProtoId entProto, string text) - { - RobustXamlLoader.Load(this); - - ItemPrototype.SetPrototype(entProto); - - NameLabel.Text = text; - } -} diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml deleted file mode 100644 index 59e0b0d890b..00000000000 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs deleted file mode 100644 index 0ea8fddae26..00000000000 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System.Numerics; -using Content.Client.UserInterface.Controls; -using Content.Shared.VendingMachines; -using Content.Shared.Cargo.Components; -using Content.Shared.Stacks; -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface.Controls; -using Content.Shared._NF.Bank; // Frontier -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Chemistry.Reagent; -using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow; -using Robust.Client.UserInterface; -using Content.Shared.IdentityManagement; -using Robust.Client.Graphics; - -namespace Content.Client.VendingMachines.UI -{ - [GenerateTypedNameReferences] - public sealed partial class VendingMachineMenu : FancyWindow - { - [Dependency] private IPrototypeManager _prototypeManager = default!; - [Dependency] private IEntityManager _entityManager = default!; - private readonly Dictionary _dummies = []; - - public event Action? OnItemSelected; - - private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) }; - - public VendingMachineMenu() - { - MinSize = SetSize = new Vector2(250, 150); - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - - VendingContents.SearchBar = SearchBar; - VendingContents.DataFilterCondition += DataFilterCondition; - VendingContents.GenerateItem += GenerateButton; - VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - // Don't clean up dummies during disposal or we'll just have to spawn them again - if (!disposing) - return; - - // Delete any dummy items we spawned - foreach (var entity in _dummies.Values) - { - _entityManager.QueueDeleteEntity(entity); - } - _dummies.Clear(); - } - - private bool DataFilterCondition(string filter, ListData data) - { - if (data is not VendorItemsListData { ItemText: var text }) - return false; - - if (string.IsNullOrEmpty(filter)) - return true; - - return text.Contains(filter, StringComparison.CurrentCultureIgnoreCase); - } - - private void GenerateButton(ListData data, ListContainerButton button) - { - if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text }) - return; - - button.AddChild(new VendingMachineItem(protoID, text)); - - button.ToolTip = text; - button.StyleBoxOverride = _styleBox; - } - - /// - /// Populates the list of available items on the vending machine interface - /// and sets icons based on their prototypes - /// - public void Populate(List inventory, float priceModifier, int balance, int? cashSlotBalance, bool requiresCash) // Frontier: add balance, cashSlotBalance - { - UpdateBalance(balance); // Frontier - UpdateCashSlotBalance(cashSlotBalance); // Frontier - - if (inventory.Count == 0 && VendingContents.Visible) - { - SearchBar.Visible = false; - VendingContents.Visible = false; - - var outOfStockLabel = new Label() - { - Text = Loc.GetString("vending-machine-component-try-eject-out-of-stock"), - Margin = new Thickness(4, 4), - HorizontalExpand = true, - VerticalAlignment = VAlignment.Stretch, - HorizontalAlignment = HAlignment.Center - }; - - MainContainer.AddChild(outOfStockLabel); - - SetSizeAfterUpdate(outOfStockLabel.Text.Length, 0); - - return; - } - - var longestEntry = string.Empty; - var listData = new List(); - - for (var i = 0; i < inventory.Count; i++) - { - var entry = inventory[i]; - - if (!_prototypeManager.TryIndex(entry.ID, out var prototype)) - continue; - - if (!_dummies.TryGetValue(entry.ID, out var dummy)) - { - dummy = _entityManager.Spawn(entry.ID); - _dummies.Add(entry.ID, dummy); - } - - var itemName = Identity.Name(dummy, _entityManager); - var cost = 0; // mono - if (requiresCash) // frontier - cost = GetPrice(entry, prototype, priceModifier); - - string itemText; - - // Frontier: unlimited vending - if (entry.Amount != uint.MaxValue) - itemText = $"[{BankSystemExtensions.ToSpesoString(cost)}] {itemName} [{entry.Amount}]"; - else - itemText = $"[{BankSystemExtensions.ToSpesoString(cost)}] {itemName}"; - // End Frontier: unlimited vending - - if (itemText.Length > longestEntry.Length) - longestEntry = itemText; - - listData.Add(new VendorItemsListData(prototype!.ID, itemText, i)); // Frontier: prototype(out var priceComponent, _entityManager.ComponentFactory)) - { - if (priceComponent.Price != 0) - { - var price = (float)priceComponent.Price; - cost = (int)(price * priceModifier); - } - else - { - if (prototype.TryGetComponent(out var stackPrice, _entityManager.ComponentFactory) - && prototype.TryGetComponent(out var stack, _entityManager.ComponentFactory)) - { - var price = stackPrice.Price * stack.Count; - cost = (int)(price * priceModifier); - } - else - cost = (int)(cost * priceModifier); - } - } - else - cost = (int)(cost * priceModifier); - - if (prototype != null && prototype.TryGetComponent(out var priceSolutions, _entityManager.ComponentFactory)) - { - if (priceSolutions.Solutions != null) - { - foreach (var solution in priceSolutions.Solutions.Values) - { - foreach (var (reagent, quantity) in solution.Contents) - { - if (!_prototypeManager.TryIndex(reagent.Prototype, - out var reagentProto)) - continue; - - // TODO check ReagentData for price information? - var costReagent = quantity.Float() * reagentProto.PricePerUnit; - cost += (int)(costReagent * priceModifier); - } - } - } - } - // End Frontier: item pricing - - // Frontier: calculate vending price (this duplicates Content.Server.PricingSystem.GetVendPrice - this should be moved to Content.Shared if possible) - if (prototype != null) - { - var price = 0.0; - - if (prototype.TryGetComponent(out var staticComp, _entityManager.ComponentFactory) && staticComp.VendPrice > 0.0) - { - price += staticComp.VendPrice; - } - else if (prototype.TryGetComponent(out var stackComp, _entityManager.ComponentFactory) && stackComp.VendPrice > 0.0) - { - price += stackComp.VendPrice; - } - - // If there is anything that explicitly sets vending price - higher OR lower, override the base. - if (price > 0.0) - { - cost = (int)price; - } - } - - return cost; - // End Frontier - } - - // Frontier - public void UpdateBalance(int balance) - { - BalanceLabel.Text = BankSystemExtensions.ToSpesoString(balance); - } - - public void UpdateCashSlotBalance(int? balance) - { - CashSlotControls.Visible = balance != null; - if (balance != null) - CashSlotLabel.Text = BankSystemExtensions.ToSpesoString(balance.Value); - } - // End Frontier - - private void SetSizeAfterUpdate(int longestEntryLength, int contentCount) - { - SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400), - Math.Clamp(contentCount * 50, 150, 350)); - } - } -} - -public record VendorItemsListData(EntProtoId ItemProtoID, string ItemText, int ItemIndex) : ListData; diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs index 6b83cccf6de..ffee486edd9 100644 --- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs +++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs @@ -1,9 +1,6 @@ -using Content.Client.UserInterface.Controls; -using Content.Client.VendingMachines.UI; +using Content.Client._Lua.VendingMachines; // LuaM using Content.Shared.VendingMachines; using Robust.Client.UserInterface; -using Robust.Shared.Input; -using System.Linq; using Robust.Client.GameObjects; using Content.Shared._NF.Bank.Components; // Frontier using Content.Shared.Containers.ItemSlots; // Frontier @@ -14,7 +11,7 @@ namespace Content.Client.VendingMachines public sealed class VendingMachineBoundUserInterface : BoundUserInterface { [ViewVariables] - private VendingMachineMenu? _menu; + private LuaVendingMachineWindow? _menu; // LuaM [ViewVariables] private List _cachedInventory = new(); @@ -49,7 +46,7 @@ protected override void Open() _mod = market.Mod; // End Frontier - _menu = this.CreateWindowCenteredLeft(); + _menu = this.CreateWindowCenteredLeft(); // LuaM // Frontier: no exceptions if (EntMan.TryGetComponent(Owner, out MetaDataComponent? meta)) _menu.Title = meta.EntityName; @@ -62,6 +59,9 @@ protected override void Open() public void Refresh() { + if (_menu == null || !EntMan.HasComponent(Owner)) + return; // исправлен древний баг торгоматов из-за которых при открытии окна + var system = EntMan.System(); _cachedInventory = system.GetAllInventory(Owner); @@ -86,26 +86,13 @@ public void Refresh() } // End Frontier - _menu?.Populate(_cachedInventory, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash + var enabled = vendingMachine is { Ejecting: false }; // LuaM + _menu?.Populate(_cachedInventory, enabled, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash } - private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data) + private void OnItemSelected(InventoryType type, string id) // LuaM { - if (args.Function != EngineKeyFunctions.UIClick) - return; - - if (data is not VendorItemsListData { ItemIndex: var itemIndex }) - return; - - if (_cachedInventory.Count == 0) - return; - - var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex); - - if (selectedItem == null) - return; - - SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID)); + SendMessage(new VendingMachineEjectMessage(type, id)); } protected override void Dispose(bool disposing) diff --git a/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs b/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs index 0c18c820f86..282a2edc958 100644 --- a/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs +++ b/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; +using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; @@ -15,10 +16,26 @@ public sealed class MarqueeLabel : Control private const float ScrollSpeedPx = 28f; private const float EndPauseSeconds = 1.25f; private const float GapPx = 32f; + private const float StartPauseSeconds = 0.35f; private readonly Label _label; private float _offset; private float _pauseRemaining; private bool _scrollingForward = true; + private bool _scrollEnabled = true; + public bool ScrollEnabled + { + get => _scrollEnabled; + set + { + if (_scrollEnabled == value) + return; + + _scrollEnabled = value; + ResetScroll(); + if (value) + _pauseRemaining = StartPauseSeconds; + } + } public string? Text { @@ -37,6 +54,12 @@ public Color? FontColorOverride set => _label.FontColorOverride = value; } + public Font? FontOverride + { + get => _label.FontOverride; + set => _label.FontOverride = value; + } + public MarqueeLabel() { RectClipContent = true; @@ -78,6 +101,8 @@ protected override Vector2 ArrangeOverride(Vector2 finalSize) protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); + if (!_scrollEnabled) + return; var avail = Size.X; var textWidth = _label.DesiredSize.X; if (textWidth <= avail + 0.5f) diff --git a/Content.Client/_Lua/Styles/LunaWindow.cs b/Content.Client/_Lua/Styles/LunaWindow.cs new file mode 100644 index 00000000000..f385714b1d5 --- /dev/null +++ b/Content.Client/_Lua/Styles/LunaWindow.cs @@ -0,0 +1,22 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp Contributors +// See AGPLv3.txt for details. + +using Content.Client.UserInterface.Controls; + +namespace Content.Client._Lua.Styles; + +[Virtual] +public class LunaWindow : FancyWindow +{ + protected void ApplyLunaChrome() + { + LunaWindowStyle.ApplyWindowChrome(this); + } + + protected override void OnThemeUpdated() + { + base.OnThemeUpdated(); + LunaWindowStyle.ApplyWindowChrome(this); + } +} diff --git a/Content.Client/_Lua/Styles/LunaWindowStyle.cs b/Content.Client/_Lua/Styles/LunaWindowStyle.cs new file mode 100644 index 00000000000..674c741e10c --- /dev/null +++ b/Content.Client/_Lua/Styles/LunaWindowStyle.cs @@ -0,0 +1,119 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp Contributors +// See AGPLv3.txt for details. + +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client._Lua.Styles; + +public static class LunaWindowStyle +{ + public static readonly Color FrameBg = Color.FromHex("#0B0F14"); + public static readonly Color FrameBorder = Color.FromHex("#243041"); + public static readonly Color TitleBarBg = Color.FromHex("#0E1218"); + public static readonly Color PanelBg = Color.FromHex("#0E1218"); + public static readonly Color PanelBorder = Color.FromHex("#2A3344"); + public static readonly Color Divider = Color.FromHex("#1C2433"); + + public static readonly Color TextMuted = Color.FromHex("#7E8BA0"); + public static readonly Color TextSecondary = Color.FromHex("#8A96A8"); + public static readonly Color TextPrimary = Color.FromHex("#C5CEDB"); + + public static readonly Color Accent = Color.FromHex("#5EC8E8"); + public static readonly Color AccentWarn = Color.FromHex("#E8A43A"); + public static readonly Color AccentBad = Color.FromHex("#E07070"); + + private static Font? _fontSmall; + private static Font? _fontBody; + private static Font? _fontTitle; + private static Font? _fontFooter; + + public static Font FontSmall => _fontSmall ??= Cache().NotoStack(size: 10); + public static Font FontBody => _fontBody ??= Cache().NotoStack(size: 12); + public static Font FontTitle => _fontTitle ??= Cache().NotoStack(variation: "Bold", size: 13); + public static Font FontFooter => _fontFooter ??= Cache().NotoStack(size: 8); + + private static IResourceCache Cache() => IoCManager.Resolve(); + + public static StyleBoxFlat Box(Color background, Color border, float borderThickness = 1f) => new() + { + BackgroundColor = background, + BorderColor = border, + BorderThickness = new Thickness(borderThickness), + }; + + public static StyleBoxFlat Shell() => Box(FrameBg, FrameBorder); + + public static StyleBoxFlat ThinDivider() => new() + { + BackgroundColor = Divider, + BorderColor = Color.Transparent, + BorderThickness = new Thickness(0), + ContentMarginTopOverride = 1, + ContentMarginBottomOverride = 1, + }; + + public static void ApplyWindowChrome(FancyWindow window) + { + if (window.ChildCount < 2) + return; + + if (window.GetChild(0) is PanelContainer frame) + { + frame.ModulateSelfOverride = Color.White; + frame.PanelOverride = Box(FrameBg, FrameBorder); + } + + if (window.GetChild(1) is not BoxContainer layout || layout.ChildCount < 2) + return; + + var titleHost = layout.GetChild(0); + if (titleHost.ChildCount > 0 && titleHost.GetChild(0) is PanelContainer titlePanel) + { + titlePanel.ModulateSelfOverride = Color.White; + titlePanel.PanelOverride = new StyleBoxFlat + { + BackgroundColor = TitleBarBg, + BorderColor = FrameBorder, + BorderThickness = new Thickness(0, 0, 0, 1), + }; + } + + if (titleHost.ChildCount > 1 && + titleHost.GetChild(1) is BoxContainer titleRow && + titleRow.ChildCount > 0 && + titleRow.GetChild(0) is Label titleLabel) + { + titleLabel.FontOverride = FontTitle; + titleLabel.FontColorOverride = TextPrimary; + } + + if (layout.GetChild(1) is PanelContainer divider) + divider.PanelOverride = ThinDivider(); + } + + public static void StyleDivider(PanelContainer panel) => panel.PanelOverride = ThinDivider(); + + public static void StyleMuted(Label label) => StyleSmall(label, TextMuted); + + public static void StyleSecondary(Label label) => StyleSmall(label, TextSecondary); + + public static void StyleValue(Label label) => StyleSmall(label, Accent); + + public static void StyleFooter(Label label) + { + label.FontOverride = FontFooter; + label.FontColorOverride = TextMuted; + } + + private static void StyleSmall(Label label, Color color) + { + label.AddStyleClass(StyleNano.StyleClassLabelSmall); + label.FontOverride = FontSmall; + label.FontColorOverride = color; + } +} diff --git a/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml new file mode 100644 index 00000000000..4fb75d01f1d --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml.cs b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml.cs new file mode 100644 index 00000000000..2e62bcf1d8d --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml.cs @@ -0,0 +1,229 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using System.Linq; +using Content.Client._Lua.Styles; +using Content.Client._Lua.VendingMachines.UI; +using Content.Shared._NF.Bank; +using Content.Shared.Cargo.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Stacks; +using Content.Shared.VendingMachines; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client._Lua.VendingMachines; + +[GenerateTypedNameReferences] +public sealed partial class LuaVendingMachineWindow : LunaWindow +{ + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private IComponentFactory _componentFactory = default!; + + private readonly List<((InventoryType Type, string Id) Key, LuaVendingEntry Row)> _rows = new(); + + public event Action? OnItemSelected; + + public LuaVendingMachineWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + ApplyLunaChrome(); + + Shell.PanelOverride = LunaWindowStyle.Shell(); + LunaWindowStyle.StyleDivider(HeaderDivider); + LunaWindowStyle.StyleDivider(FooterDivider); + LunaWindowStyle.StyleMuted(BalanceCaption); + LunaWindowStyle.StyleValue(BalanceLabel); + LunaWindowStyle.StyleMuted(CashSlotCaption); + LunaWindowStyle.StyleValue(CashSlotLabel); + LunaWindowStyle.StyleSecondary(SearchLabel); + LunaWindowStyle.StyleMuted(EmptyLabel); + LunaWindowStyle.StyleFooter(FlavorLeft); + LunaWindowStyle.StyleFooter(FlavorRight); + + Search.OnTextChanged += args => ApplySearchFilter(args.Text); + } + + public void Populate(List inventory, + bool enabled, + float priceModifier, + int balance, + int? cashSlotBalance, + bool requiresCash) + { + UpdateBalance(balance); + UpdateCashSlotBalance(cashSlotBalance); + + var hasItems = inventory.Count > 0; + Search.Visible = hasItems; + SearchLabel.Visible = hasItems; + CatalogScroll.Visible = hasItems; + EmptyLabel.Visible = !hasItems; + + if (SameItems(inventory)) + { + for (var i = 0; i < inventory.Count; i++) + { + if (_prototypeManager.TryIndex(inventory[i].ID, out var prototype)) + ApplyRowState(_rows[i].Row, inventory[i], prototype, enabled, priceModifier, requiresCash); + } + + return; + } + + _rows.Clear(); + Entries.DisposeAllChildren(); + + foreach (var entry in inventory) + { + if (!_prototypeManager.TryIndex(entry.ID, out var prototype)) + continue; + + var row = CreateRow(entry, prototype); + ApplyRowState(row, entry, prototype, enabled, priceModifier, requiresCash); + _rows.Add(((entry.Type, entry.ID), row)); + Entries.AddChild(row); + } + + ApplySearchFilter(Search.Text); + } + + public void UpdateBalance(int balance) + { + BalanceLabel.Text = BankSystemExtensions.ToSpesoString(balance); + } + + public void UpdateCashSlotBalance(int? balance) + { + CashSlotControls.Visible = balance != null; + if (balance != null) + CashSlotLabel.Text = BankSystemExtensions.ToSpesoString(balance.Value); + } + + private bool SameItems(List inventory) + { + if (_rows.Count != inventory.Count) + return false; + + for (var i = 0; i < inventory.Count; i++) + { + if (_rows[i].Key != (inventory[i].Type, inventory[i].ID)) + return false; + } + + return true; + } + + private LuaVendingEntry CreateRow(VendingMachineInventoryEntry entry, EntityPrototype prototype) + { + var row = new LuaVendingEntry(); + row.Icon.SetPrototype(prototype.ID); + row.Panel.Title.Text = prototype.Name; + + switch (entry.Type) + { + case InventoryType.Emagged: + row.Panel.BorderColor = LunaWindowStyle.AccentBad; + row.Panel.HoveredColor = LunaWindowStyle.AccentBad; + break; + case InventoryType.Contraband: + row.Panel.BorderColor = LunaWindowStyle.AccentWarn; + row.Panel.HoveredColor = LunaWindowStyle.AccentWarn; + break; + } + + var name = prototype.Name; + var description = prototype.Description; + row.Panel.Button.TooltipDelay = 0; + row.Panel.Button.TooltipSupplier = _ => + { + var tooltipText = new FormattedMessage(); + tooltipText.AddText(name); + if (!string.IsNullOrWhiteSpace(description)) + { + tooltipText.PushNewline(); + tooltipText.AddText(description); + } + + var tooltip = new Tooltip(); + tooltip.SetMessage(tooltipText); + return tooltip; + }; + + var type = entry.Type; + var id = entry.ID; + row.Panel.Button.OnPressed += _ => OnItemSelected?.Invoke(type, id); + return row; + } + + private void ApplyRowState(LuaVendingEntry row, + VendingMachineInventoryEntry entry, + EntityPrototype prototype, + bool enabled, + float priceModifier, + bool requiresCash) + { + var cost = requiresCash ? GetPrice(prototype, priceModifier) : 0; // Mono: free vending shows no price + row.Price.Text = BankSystemExtensions.ToSpesoString(cost); + row.Amount.Text = entry.Amount == uint.MaxValue ? string.Empty : entry.Amount.ToString(); // Frontier: unlimited vending + row.SetDisabled(!enabled || entry.Amount == 0); + } + + private void ApplySearchFilter(string? text) + { + foreach (var (_, row) in _rows) + { + row.Visible = string.IsNullOrWhiteSpace(text) || + (row.Panel.Title.Text?.Contains(text, StringComparison.CurrentCultureIgnoreCase) ?? false); + } + } + + private int GetPrice(EntityPrototype prototype, float priceModifier) + { + var vendPrice = 0.0; + if (prototype.TryGetComponent(out var staticVend, _componentFactory) && staticVend.VendPrice > 0.0) + vendPrice = staticVend.VendPrice; + else if (prototype.TryGetComponent(out var stackVend, _componentFactory) && stackVend.VendPrice > 0.0) + vendPrice = stackVend.VendPrice; + + if (vendPrice > 0.0) + return (int) vendPrice; + + var cost = 20; + if (prototype.TryGetComponent(out var priceComponent, _componentFactory)) + { + if (priceComponent.Price != 0) + cost = (int) ((float) priceComponent.Price * priceModifier); + else if (prototype.TryGetComponent(out var stackPrice, _componentFactory) + && prototype.TryGetComponent(out var stack, _componentFactory)) + cost = (int) (stackPrice.Price * stack.Count * priceModifier); + else + cost = (int) (cost * priceModifier); + } + else + { + cost = (int) (cost * priceModifier); + } + + if (prototype.TryGetComponent(out var solutions, _componentFactory) + && solutions.Solutions != null) + { + foreach (var solution in solutions.Solutions.Values) + { + foreach (var (reagent, quantity) in solution.Contents) + { + if (_prototypeManager.TryIndex(reagent.Prototype, out var reagentProto)) + cost += (int) (quantity.Float() * reagentProto.PricePerUnit * priceModifier); + } + } + } + + return cost; + } +} diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml new file mode 100644 index 00000000000..a85aafbdcde --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml @@ -0,0 +1,4 @@ + + diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs new file mode 100644 index 00000000000..f113592c0e0 --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs @@ -0,0 +1,27 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Lua.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class LuaVendingButton : Button +{ + public event Action? OnDrawModeChanged; + + public LuaVendingButton() + { + RobustXamlLoader.Load(this); + Label.Visible = false; + ClipText = true; + } + + protected override void DrawModeChanged() + { + OnDrawModeChanged?.Invoke(); + } +} diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml new file mode 100644 index 00000000000..4d7dea40378 --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml.cs b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml.cs new file mode 100644 index 00000000000..5d6483f9e7e --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml.cs @@ -0,0 +1,32 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using Content.Client._Lua.Styles; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Lua.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class LuaVendingEntry : Control +{ + public LuaVendingEntry() + { + RobustXamlLoader.Load(this); + Price.FontOverride = LunaWindowStyle.FontSmall; + Amount.FontOverride = LunaWindowStyle.FontSmall; + Panel.Title.FontOverride = LunaWindowStyle.FontBody; + SetDisabled(false); + } + + public void SetDisabled(bool disabled) + { + Panel.Button.Disabled = disabled; + Amount.FontColorOverride = disabled ? LunaWindowStyle.AccentBad : LunaWindowStyle.TextSecondary; + Price.FontColorOverride = disabled ? LunaWindowStyle.TextMuted : LunaWindowStyle.Accent; + Panel.Title.FontColorOverride = disabled ? LunaWindowStyle.TextMuted : LunaWindowStyle.TextPrimary; + Panel.UpdateColor(); + } +} diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml b/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml new file mode 100644 index 00000000000..ec00b454ec9 --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml.cs b/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml.cs new file mode 100644 index 00000000000..13b8d0c4ec4 --- /dev/null +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingPanel.xaml.cs @@ -0,0 +1,53 @@ +// LuaCorp - This file is licensed under AGPLv3 +// Copyright (c) 2026 LuaCorp +// See AGPLv3.txt for details. + +using Content.Client._Lua.Styles; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Lua.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class LuaVendingPanel : Control +{ + private static readonly Color HoveredBg = Color.FromHex("#163040"); + private static readonly Color DisabledBg = Color.FromHex("#1C2433"); + + public Color BorderColor = LunaWindowStyle.PanelBorder; + public Color HoveredColor = LunaWindowStyle.Accent; + + public LuaVendingPanel() + { + RobustXamlLoader.Load(this); + Button.OnDrawModeChanged += UpdateColor; + + Title.ScrollEnabled = false; + Button.OnMouseEntered += _ => Title.ScrollEnabled = true; + Button.OnMouseExited += _ => Title.ScrollEnabled = false; + + UpdateColor(); + } + + public void UpdateColor() + { + var panel = (StyleBoxFlat) Panel.PanelOverride!; + if (Button.Disabled) + { + panel.BackgroundColor = DisabledBg; + panel.BorderColor = LunaWindowStyle.TextMuted; + return; + } + + panel.BackgroundColor = Button.IsHovered ? HoveredBg : LunaWindowStyle.PanelBg; + panel.BorderColor = Button.IsHovered ? HoveredColor : BorderColor; + } + + protected override void EnteredTree() + { + base.EnteredTree(); + UpdateColor(); + } +} diff --git a/Resources/Locale/en-US/_Lua/vending.ftl b/Resources/Locale/en-US/_Lua/vending.ftl new file mode 100644 index 00000000000..052e2d86e76 --- /dev/null +++ b/Resources/Locale/en-US/_Lua/vending.ftl @@ -0,0 +1 @@ +lua-vending-search-label = Search diff --git a/Resources/Locale/ru-RU/_Lua/vending.ftl b/Resources/Locale/ru-RU/_Lua/vending.ftl new file mode 100644 index 00000000000..f4285a12715 --- /dev/null +++ b/Resources/Locale/ru-RU/_Lua/vending.ftl @@ -0,0 +1 @@ +lua-vending-search-label = Поиск From 7d9e9a28772889290276f921ce094fe7cabbd008 Mon Sep 17 00:00:00 2001 From: C_Quarian Date: Mon, 14 Sep 2026 17:58:43 +0400 Subject: [PATCH 2/2] =?UTF-8?q?=D0=A7=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8?= =?UTF-8?q?=D0=B5=D0=B2=20=D0=B8=20=D0=BB=D0=B8=D1=86=D0=B5=D0=BD=D0=B7?= =?UTF-8?q?=D0=B8=D0=B8=20(=D0=BE=D0=BD=D0=B0=20=D0=BD=D0=B5=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D1=83=20=D0=BD=D0=B0=D1=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VendingMachineBoundUserInterface.cs | 12 ++++++------ Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs | 4 ---- Content.Client/_Lua/Styles/LunaWindow.cs | 4 ---- Content.Client/_Lua/Styles/LunaWindowStyle.cs | 4 ---- .../VendingMachines/LuaVendingMachineWindow.xaml | 1 - .../VendingMachines/LuaVendingMachineWindow.xaml.cs | 4 ---- .../_Lua/VendingMachines/UI/LuaVendingButton.xaml | 1 - .../_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs | 4 ---- .../_Lua/VendingMachines/UI/LuaVendingEntry.xaml | 1 - .../_Lua/VendingMachines/UI/LuaVendingEntry.xaml.cs | 4 ---- .../_Lua/VendingMachines/UI/LuaVendingPanel.xaml | 1 - .../_Lua/VendingMachines/UI/LuaVendingPanel.xaml.cs | 4 ---- 12 files changed, 6 insertions(+), 38 deletions(-) diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs index ffee486edd9..04719def2d1 100644 --- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs +++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs @@ -11,7 +11,7 @@ namespace Content.Client.VendingMachines public sealed class VendingMachineBoundUserInterface : BoundUserInterface { [ViewVariables] - private LuaVendingMachineWindow? _menu; // LuaM + private LuaVendingMachineWindow? _menu; // LuaM: VendingMachineMenu > LuaVendingMachineWindow [ViewVariables] private List _cachedInventory = new(); @@ -46,7 +46,7 @@ protected override void Open() _mod = market.Mod; // End Frontier - _menu = this.CreateWindowCenteredLeft(); // LuaM + _menu = this.CreateWindowCenteredLeft(); // LuaM: VendingMachineMenu > LuaVendingMachineWindow // Frontier: no exceptions if (EntMan.TryGetComponent(Owner, out MetaDataComponent? meta)) _menu.Title = meta.EntityName; @@ -59,8 +59,8 @@ protected override void Open() public void Refresh() { - if (_menu == null || !EntMan.HasComponent(Owner)) - return; // исправлен древний баг торгоматов из-за которых при открытии окна + if (_menu == null || !EntMan.HasComponent(Owner)) // LuaM + return; // LuaM var system = EntMan.System(); _cachedInventory = system.GetAllInventory(Owner); @@ -87,10 +87,10 @@ public void Refresh() // End Frontier var enabled = vendingMachine is { Ejecting: false }; // LuaM - _menu?.Populate(_cachedInventory, enabled, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash + _menu?.Populate(_cachedInventory, enabled, _mod, _balance, cashSlotValue, _requiresCash); // Frontier: add _balance, mono: add _requiresCash // LuaM: add enabled } - private void OnItemSelected(InventoryType type, string id) // LuaM + private void OnItemSelected(InventoryType type, string id) // LuaM: GUIBoundKeyEventArgs, ListData > InventoryType, string { SendMessage(new VendingMachineEjectMessage(type, id)); } diff --git a/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs b/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs index 282a2edc958..94c74b827be 100644 --- a/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs +++ b/Content.Client/_Lua/AmmoLoader/UI/MarqueeLabel.cs @@ -1,7 +1,3 @@ -// LuaCorp - This file is licensed under AGPLv3 -// Copyright (c) 2026 LuaCorp Contributors -// See AGPLv3.txt for details. - using System; using System.Numerics; using Robust.Client.Graphics; diff --git a/Content.Client/_Lua/Styles/LunaWindow.cs b/Content.Client/_Lua/Styles/LunaWindow.cs index f385714b1d5..7644c0ea8dc 100644 --- a/Content.Client/_Lua/Styles/LunaWindow.cs +++ b/Content.Client/_Lua/Styles/LunaWindow.cs @@ -1,7 +1,3 @@ -// LuaCorp - This file is licensed under AGPLv3 -// Copyright (c) 2026 LuaCorp Contributors -// See AGPLv3.txt for details. - using Content.Client.UserInterface.Controls; namespace Content.Client._Lua.Styles; diff --git a/Content.Client/_Lua/Styles/LunaWindowStyle.cs b/Content.Client/_Lua/Styles/LunaWindowStyle.cs index 674c741e10c..d8642943c3e 100644 --- a/Content.Client/_Lua/Styles/LunaWindowStyle.cs +++ b/Content.Client/_Lua/Styles/LunaWindowStyle.cs @@ -1,7 +1,3 @@ -// LuaCorp - This file is licensed under AGPLv3 -// Copyright (c) 2026 LuaCorp Contributors -// See AGPLv3.txt for details. - using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Robust.Client.Graphics; diff --git a/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml index 4fb75d01f1d..f6e2d441ec4 100644 --- a/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml +++ b/Content.Client/_Lua/VendingMachines/LuaVendingMachineWindow.xaml @@ -1,4 +1,3 @@ - diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs index f113592c0e0..a6cfd5e94be 100644 --- a/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingButton.xaml.cs @@ -1,7 +1,3 @@ -// LuaCorp - This file is licensed under AGPLv3 -// Copyright (c) 2026 LuaCorp -// See AGPLv3.txt for details. - using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; diff --git a/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml index 4d7dea40378..0beafb7c627 100644 --- a/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml +++ b/Content.Client/_Lua/VendingMachines/UI/LuaVendingEntry.xaml @@ -1,4 +1,3 @@ -