diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs index e89d981c1d..96e4c51dd3 100644 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs +++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Numerics; using Content.Client.Stylesheets; using Content.Shared.VendingMachines; @@ -10,186 +9,184 @@ using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow; using Robust.Client.UserInterface; using Content.Client.UserInterface.Controls; using Content.Shared.IdentityManagement; -using Robust.Client.Graphics; using Robust.Shared.Utility; -namespace Content.Client.VendingMachines.UI +namespace Content.Client.VendingMachines.UI; + +[GenerateTypedNameReferences] +public sealed partial class VendingMachineMenu : FancyWindow { - [GenerateTypedNameReferences] - public sealed partial class VendingMachineMenu : FancyWindow + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private IEntityManager _entityManager = default!; + + private readonly Dictionary _dummies = []; + private readonly Dictionary _listItems = new(); + private readonly Dictionary _amounts = new(); + + /// + /// Whether the vending machine is able to be interacted with or not. + /// + private bool _enabled; + + public event Action? OnItemSelected; + + public VendingMachineMenu() { - [Dependency] private IPrototypeManager _prototypeManager = default!; - [Dependency] private IEntityManager _entityManager = default!; + MinSize = SetSize = new Vector2(250, 150); + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); - private readonly Dictionary _dummies = []; - private readonly Dictionary _listItems = new(); - private readonly Dictionary _amounts = new(); + VendingContents.SearchBar = SearchBar; + VendingContents.DataFilterCondition += DataFilterCondition; + VendingContents.GenerateItem += GenerateButton; + VendingContents.ItemKeyBindDown += (args, data) => OnItemSelected?.Invoke(args, data); + } - /// - /// Whether the vending machine is able to be interacted with or not. - /// - private bool _enabled; + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - public event Action? OnItemSelected; + // Don't clean up dummies during disposal or we'll just have to spawn them again + if (!disposing) + return; - public VendingMachineMenu() + // Delete any dummy items we spawned + foreach (var entity in _dummies.Values) { - 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); + _entityManager.QueueDeleteEntity(entity); } + _dummies.Clear(); + } - protected override void Dispose(bool disposing) + 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; + + var item = new VendingMachineItem(protoID, text); + _listItems[protoID] = (button, item); + button.AddChild(item); + button.AddStyleClass(StyleClass.ButtonSquare); + button.Disabled = !_enabled || _amounts[protoID] == 0; + } + + /// + /// Populates the list of available items on the vending machine interface + /// and sets icons based on their prototypes + /// + public void Populate(List inventory, bool enabled) + { + _enabled = enabled; + _listItems.Clear(); + _amounts.Clear(); + + if (inventory.Count == 0 && VendingContents.Visible) { - base.Dispose(disposing); + SearchBar.Visible = false; + VendingContents.Visible = false; - // 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) + var outOfStockLabel = new Label { - _entityManager.QueueDeleteEntity(entity); - } - _dummies.Clear(); + 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; } - private bool DataFilterCondition(string filter, ListData data) + var longestEntry = string.Empty; + var listData = new List(); + + for (var i = 0; i < inventory.Count; i++) { - if (data is not VendorItemsListData { ItemText: var text }) - return false; + var entry = inventory[i]; - 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; - - var item = new VendingMachineItem(protoID, text); - _listItems[protoID] = (button, item); - button.AddChild(item); - button.AddStyleClass(StyleClass.ButtonSquare); - button.Disabled = !_enabled || _amounts[protoID] == 0; - } - - /// - /// Populates the list of available items on the vending machine interface - /// and sets icons based on their prototypes - /// - public void Populate(List inventory, bool enabled) - { - _enabled = enabled; - _listItems.Clear(); - _amounts.Clear(); - - if (inventory.Count == 0 && VendingContents.Visible) + if (!_prototypeManager.Resolve(entry.ID, out var prototype)) { - 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; + _amounts[entry.ID] = 0; + continue; } - var longestEntry = string.Empty; - var listData = new List(); - - for (var i = 0; i < inventory.Count; i++) + if (!_dummies.TryGetValue(entry.ID, out var dummy)) { - var entry = inventory[i]; - - if (!_prototypeManager.Resolve(entry.ID, out var prototype)) - { - _amounts[entry.ID] = 0; - 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 itemText = $"{itemName} [{entry.Amount}]"; - _amounts[entry.ID] = entry.Amount; - - if (itemText.Length > longestEntry.Length) - longestEntry = itemText; - - listData.Add(new VendorItemsListData(prototype.ID, i) - { - ItemText = itemText, - }); + dummy = _entityManager.Spawn(entry.ID); + _dummies.Add(entry.ID, dummy); } - VendingContents.PopulateList(listData); - - SetSizeAfterUpdate(longestEntry.Length, inventory.Count); - } - - /// - /// Updates text entries for vending data in place without modifying the list controls. - /// - public void UpdateAmounts(List cachedInventory, bool enabled) - { - _enabled = enabled; - - foreach (var proto in _dummies.Keys) - { - if (!_listItems.TryGetValue(proto, out var button)) - continue; - - var dummy = _dummies[proto]; - if (!cachedInventory.TryFirstOrDefault(o => o.ID == proto, out var entry)) - continue; - var amount = entry.Amount; - // Could be better? Problem is all inventory entries get squashed. - var text = GetItemText(dummy, amount); - - button.Item.SetText(text); - button.Button.Disabled = !enabled || amount == 0; - } - } - - private string GetItemText(EntityUid dummy, uint amount) - { var itemName = Identity.Name(dummy, _entityManager); - return $"{itemName} [{amount}]"; + var itemText = $"{itemName} [{entry.Amount}]"; + _amounts[entry.ID] = entry.Amount; + + if (itemText.Length > longestEntry.Length) + longestEntry = itemText; + + listData.Add(new VendorItemsListData(prototype.ID, i) + { + ItemText = itemText, + }); } - private void SetSizeAfterUpdate(int longestEntryLength, int contentCount) + VendingContents.PopulateList(listData); + + SetSizeAfterUpdate(longestEntry.Length, inventory.Count); + } + + /// + /// Updates text entries for vending data in place without modifying the list controls. + /// + public void UpdateAmounts(List cachedInventory, bool enabled) + { + _enabled = enabled; + + foreach (var proto in _dummies.Keys) { - SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400), - Math.Clamp(contentCount * 50, 150, 350)); + if (!_listItems.TryGetValue(proto, out var button)) + continue; + + var dummy = _dummies[proto]; + if (!cachedInventory.TryFirstOrDefault(o => o.ID == proto, out var entry)) + continue; + var amount = entry.Amount; + // Could be better? Problem is all inventory entries get squashed. + var text = GetItemText(dummy, amount); + + button.Item.SetText(text); + button.Button.Disabled = !enabled || amount == 0; } } - public record VendorItemsListData(EntProtoId ItemProtoID, int ItemIndex) : ListData + private string GetItemText(EntityUid dummy, uint amount) { - public string ItemText = string.Empty; + var itemName = Identity.Name(dummy, _entityManager); + return $"{itemName} [{amount}]"; + } + + 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, int ItemIndex) : ListData +{ + public string ItemText = string.Empty; +} diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs index 874808158d..c5e138b7eb 100644 --- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs +++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs @@ -5,80 +5,75 @@ using Robust.Client.UserInterface; using Robust.Shared.Input; using System.Linq; -namespace Content.Client.VendingMachines +namespace Content.Client.VendingMachines; + +public sealed class VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { - public sealed class VendingMachineBoundUserInterface : BoundUserInterface + [ViewVariables] + private VendingMachineMenu? _menu; + + [ViewVariables] + private List _cachedInventory = new(); + + protected override void Open() { - [ViewVariables] - private VendingMachineMenu? _menu; + base.Open(); - [ViewVariables] - private List _cachedInventory = new(); + _menu = this.CreateWindowCenteredLeft(); + _menu.Title = EntMan.GetComponent(Owner).EntityName; + _menu.OnItemSelected += OnItemSelected; + Refresh(); + } - public VendingMachineBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } + public void Refresh() + { + var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting; - protected override void Open() - { - base.Open(); + var system = EntMan.System(); + _cachedInventory = system.GetAllInventory(Owner); - _menu = this.CreateWindowCenteredLeft(); - _menu.Title = EntMan.GetComponent(Owner).EntityName; - _menu.OnItemSelected += OnItemSelected; - Refresh(); - } + _menu?.Populate(_cachedInventory, enabled); + } - public void Refresh() - { - var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting; + public void UpdateAmounts() + { + var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting; - var system = EntMan.System(); - _cachedInventory = system.GetAllInventory(Owner); + var system = EntMan.System(); + _cachedInventory = system.GetAllInventory(Owner); + _menu?.UpdateAmounts(_cachedInventory, enabled); + } - _menu?.Populate(_cachedInventory, enabled); - } + private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data) + { + if (args.Function != EngineKeyFunctions.UIClick) + return; - public void UpdateAmounts() - { - var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting; + if (data is not VendorItemsListData { ItemIndex: var itemIndex }) + return; - var system = EntMan.System(); - _cachedInventory = system.GetAllInventory(Owner); - _menu?.UpdateAmounts(_cachedInventory, enabled); - } + if (_cachedInventory.Count == 0) + return; - private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data) - { - if (args.Function != EngineKeyFunctions.UIClick) - return; + var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex); - if (data is not VendorItemsListData { ItemIndex: var itemIndex }) - return; + if (selectedItem == null) + return; - if (_cachedInventory.Count == 0) - return; + SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID)); + } - var selectedItem = _cachedInventory.ElementAtOrDefault(itemIndex); + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; - if (selectedItem == null) - return; + if (_menu == null) + return; - SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID)); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - return; - - if (_menu == null) - return; - - _menu.OnItemSelected -= OnItemSelected; - _menu.OnClose -= Close; - _menu.Dispose(); - } + _menu.OnItemSelected -= OnItemSelected; + _menu.OnClose -= Close; + _menu.Dispose(); } } diff --git a/Content.Client/VendingMachines/VendingMachineSystem.cs b/Content.Client/VendingMachines/VendingMachineSystem.cs index 7ddfacd626..e0a21812e7 100644 --- a/Content.Client/VendingMachines/VendingMachineSystem.cs +++ b/Content.Client/VendingMachines/VendingMachineSystem.cs @@ -12,15 +12,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem [Dependency] private SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private SpriteSystem _sprite = default!; - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAppearanceChange); - SubscribeLocalEvent(OnAnimationCompleted); - SubscribeLocalEvent(OnVendingHandleState); - } - + [SubscribeLocalEvent] private void OnVendingHandleState(Entity entity, ref ComponentHandleState args) { if (args.Current is not VendingMachineComponentState state) @@ -85,6 +77,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem } } + [SubscribeLocalEvent] private void OnAnimationCompleted(EntityUid uid, VendingMachineComponent component, AnimationCompletedEvent args) { if (!TryComp(uid, out var sprite)) @@ -99,6 +92,7 @@ public sealed partial class VendingMachineSystem : SharedVendingMachineSystem UpdateAppearance(uid, visualState, component, sprite); } + [SubscribeLocalEvent] private void OnAppearanceChange(EntityUid uid, VendingMachineComponent component, ref AppearanceChangeEvent args) { if (args.Sprite == null) diff --git a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs index 7f2807fe7b..6d9c3fd3fc 100644 --- a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs +++ b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs @@ -14,7 +14,6 @@ using Content.Shared.Storage.EntitySystems; using Content.Shared.VendingMachines; using Content.Shared.Wires; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests diff --git a/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs b/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs index 39231fbe83..4a548713d5 100644 --- a/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs +++ b/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs @@ -11,8 +11,8 @@ public sealed partial class VendingMachineContrabandWireAction : BaseToggleWireA public override Color Color { get; set; } = Color.Green; public override string Name { get; set; } = "wire-name-vending-contraband"; - public override object? StatusKey { get; } = ContrabandWireKey.StatusKey; - public override object? TimeoutKey { get; } = ContrabandWireKey.TimeoutKey; + public override object StatusKey => ContrabandWireKey.StatusKey; + public override object TimeoutKey => ContrabandWireKey.TimeoutKey; public override void Initialize() { diff --git a/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs b/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs index 45e5e96426..adff62cfa5 100644 --- a/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs +++ b/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs @@ -11,7 +11,7 @@ public sealed partial class VendingMachineEjectItemWireAction : ComponentWireAct public override Color Color { get; set; } = Color.Red; public override string Name { get; set; } = "wire-name-vending-eject"; - public override object? StatusKey { get; } = EjectWireKey.StatusKey; + public override object StatusKey => EjectWireKey.StatusKey; public override StatusLightState? GetLightState(Wire wire, VendingMachineComponent comp) => comp.CanShoot ? StatusLightState.BlinkingFast : StatusLightState.On; diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index ea66c37f62..30ab1af294 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -13,235 +13,226 @@ using Content.Shared.Wall; using Robust.Shared.Prototypes; using Robust.Shared.Random; -namespace Content.Server.VendingMachines +namespace Content.Server.VendingMachines; + +public sealed partial class VendingMachineSystem : SharedVendingMachineSystem { - public sealed partial class VendingMachineSystem : SharedVendingMachineSystem + [Dependency] private IRobustRandom _random = default!; + [Dependency] private PricingSystem _pricing = default!; + [Dependency] private ThrowingSystem _throwingSystem = default!; + + private const float WallVendEjectDistanceFromWall = 1f; + + [SubscribeLocalEvent] + private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args) { - [Dependency] private IRobustRandom _random = default!; - [Dependency] private PricingSystem _pricing = default!; - [Dependency] private ThrowingSystem _throwingSystem = default!; + var price = 0.0; - private const float WallVendEjectDistanceFromWall = 1f; - - public override void Initialize() + foreach (var entry in component.Inventory.Values) { - base.Initialize(); - - SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent(OnVendingPrice); - SubscribeLocalEvent(OnTryVocalize); - - SubscribeLocalEvent(OnSelfDispense); - - SubscribeLocalEvent(OnPriceCalculation); - } - - private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args) - { - var price = 0.0; - - foreach (var entry in component.Inventory.Values) + if (!ProtoMan.TryIndex(entry.ID, out var proto)) { - if (!ProtoMan.TryIndex(entry.ID, out var proto)) - { - Log.Error($"Unable to find entity prototype {entry.ID} on {ToPrettyString(uid)} vending."); - continue; - } - - price += entry.Amount * _pricing.GetEstimatedPrice(proto); + Log.Error($"Unable to find entity prototype {entry.ID} on {ToPrettyString(uid)} vending."); + continue; } - args.Price += price; + price += entry.Amount * _pricing.GetEstimatedPrice(proto); } - protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args) - { - base.OnMapInit(uid, component, args); + args.Price += price; + } - if (HasComp(uid)) - { - TryUpdateVisualState((uid, component)); - } - } + protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args) + { + base.OnMapInit(uid, component, args); - private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, ref PowerChangedEvent args) + if (HasComp(uid)) { TryUpdateVisualState((uid, component)); } + } - private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args) + [SubscribeLocalEvent] + private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, ref PowerChangedEvent args) + { + TryUpdateVisualState((uid, component)); + } + + [SubscribeLocalEvent] + private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args) + { + if (!args.DamageIncreased && component.Broken) { - if (!args.DamageIncreased && component.Broken) - { - component.Broken = false; - Dirty(uid, component); - TryUpdateVisualState((uid, component)); - return; - } - - if (component.Broken || component.DispenseOnHitCoolingDown || - component.DispenseOnHitChance == null || args.DamageDelta == null) - return; - - if (args.DamageIncreased && args.DamageDelta.GetTotal() >= component.DispenseOnHitThreshold && - _random.Prob(component.DispenseOnHitChance.Value)) - { - if (component.DispenseOnHitCooldown != null) - { - component.DispenseOnHitEnd = Timing.CurTime + component.DispenseOnHitCooldown.Value; - } - - EjectRandom(uid, throwItem: true, forceEject: true, component); - } - } - - private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args) - { - if (args.Handled) - return; - - args.Handled = true; - EjectRandom(uid, throwItem: true, forceEject: false, component); - } - - /// - /// Sets the property of the vending machine. - /// - public void SetShooting(EntityUid uid, bool canShoot, VendingMachineComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.CanShoot = canShoot; - } - - /// - /// Sets the property of the vending machine. - /// - public void SetContraband(EntityUid uid, bool contraband, VendingMachineComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.Contraband = contraband; + component.Broken = false; Dirty(uid, component); + TryUpdateVisualState((uid, component)); + return; } - /// - /// Ejects a random item from the available stock. Will do nothing if the vending machine is empty. - /// - /// - /// Whether to throw the item in a random direction after dispensing it. - /// Whether to skip the regular ejection checks and immediately dispense the item without animation. - /// - public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null) + if (component.Broken || component.DispenseOnHitCoolingDown || + component.DispenseOnHitChance == null || args.DamageDelta == null) + return; + + if (args.DamageIncreased && args.DamageDelta.GetTotal() >= component.DispenseOnHitThreshold && + _random.Prob(component.DispenseOnHitChance.Value)) { - if (!Resolve(uid, ref vendComponent)) - return; - - var availableItems = GetAvailableInventory(uid, vendComponent); - if (availableItems.Count <= 0) - return; - - var item = _random.Pick(availableItems); - - if (forceEject) + if (component.DispenseOnHitCooldown != null) { - vendComponent.NextItemToEject = item.ID; - vendComponent.ThrowNextItem = throwItem; - var entry = GetEntry(uid, item.ID, item.Type, vendComponent); - if (entry != null) - entry.Amount--; - EjectItem(uid, vendComponent, forceEject); - } - else - { - TryEjectVendorItem(uid, item.Type, item.ID, throwItem, user: null, vendComponent: vendComponent); - } - } - - protected override void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false) - { - if (!Resolve(uid, ref vendComponent)) - return; - - // No need to update the visual state because we never changed it during a forced eject - if (!forceEject) - TryUpdateVisualState((uid, vendComponent)); - - if (string.IsNullOrEmpty(vendComponent.NextItemToEject)) - { - vendComponent.ThrowNextItem = false; - return; + component.DispenseOnHitEnd = Timing.CurTime + component.DispenseOnHitCooldown.Value; } - // Default spawn coordinates - var xform = Transform(uid); - var spawnCoordinates = xform.Coordinates; - - //Make sure the wallvends spawn outside of the wall. - if (TryComp(uid, out var wallMountComponent)) - { - var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * WallVendEjectDistanceFromWall; - spawnCoordinates = spawnCoordinates.Offset(offset); - } - - var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates); - - if (vendComponent.ThrowNextItem) - { - var range = vendComponent.NonLimitedEjectRange; - var direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range)); - _throwingSystem.TryThrow(ent, direction, vendComponent.NonLimitedEjectForce); - } - - vendComponent.NextItemToEject = null; - vendComponent.ThrowNextItem = false; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var disabled = EntityQueryEnumerator(); - while (disabled.MoveNext(out var uid, out _, out var comp)) - { - if (comp.NextEmpEject < Timing.CurTime) - { - EjectRandom(uid, true, false, comp); - comp.NextEmpEject += (5 * comp.EjectDelay); - } - } - } - - private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args) - { - List priceSets = new(); - - // Find the most expensive inventory and use that as the highest price. - foreach (var vendingInventory in component.CanRestock) - { - double total = 0; - - if (ProtoMan.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype)) - { - foreach (var (item, amount) in inventoryPrototype.StartingInventory) - { - if (ProtoMan.TryIndex(item, out EntityPrototype? entity)) - total += _pricing.GetEstimatedPrice(entity) * amount; - } - } - - priceSets.Add(total); - } - - args.Price += priceSets.Max(); - } - - private void OnTryVocalize(Entity ent, ref TryVocalizeEvent args) - { - args.Cancelled |= ent.Comp.Broken; + EjectRandom(uid, throwItem: true, forceEject: true, component); } } + + [SubscribeLocalEvent] + private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + EjectRandom(uid, throwItem: true, forceEject: false, component); + } + + /// + /// Sets the property of the vending machine. + /// + public void SetShooting(EntityUid uid, bool canShoot, VendingMachineComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.CanShoot = canShoot; + } + + /// + /// Sets the property of the vending machine. + /// + public void SetContraband(EntityUid uid, bool contraband, VendingMachineComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.Contraband = contraband; + Dirty(uid, component); + } + + /// + /// Ejects a random item from the available stock. Will do nothing if the vending machine is empty. + /// + /// + /// Whether to throw the item in a random direction after dispensing it. + /// Whether to skip the regular ejection checks and immediately dispense the item without animation. + /// + public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + var availableItems = GetAvailableInventory(uid, vendComponent); + if (availableItems.Count <= 0) + return; + + var item = _random.Pick(availableItems); + + if (forceEject) + { + vendComponent.NextItemToEject = item.ID; + vendComponent.ThrowNextItem = throwItem; + var entry = GetEntry(uid, item.ID, item.Type, vendComponent); + if (entry != null) + entry.Amount--; + EjectItem(uid, vendComponent, forceEject); + } + else + { + TryEjectVendorItem(uid, item.Type, item.ID, throwItem, user: null, vendComponent: vendComponent); + } + } + + protected override void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false) + { + if (!Resolve(uid, ref vendComponent)) + return; + + // No need to update the visual state because we never changed it during a forced eject + if (!forceEject) + TryUpdateVisualState((uid, vendComponent)); + + if (string.IsNullOrEmpty(vendComponent.NextItemToEject)) + { + vendComponent.ThrowNextItem = false; + return; + } + + // Default spawn coordinates + var xform = Transform(uid); + var spawnCoordinates = xform.Coordinates; + + //Make sure the wallvends spawn outside of the wall. + if (TryComp(uid, out var wallMountComponent)) + { + var offset = (wallMountComponent.Direction + xform.LocalRotation - Math.PI / 2).ToVec() * WallVendEjectDistanceFromWall; + spawnCoordinates = spawnCoordinates.Offset(offset); + } + + var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates); + + if (vendComponent.ThrowNextItem) + { + var range = vendComponent.NonLimitedEjectRange; + var direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range)); + _throwingSystem.TryThrow(ent, direction, vendComponent.NonLimitedEjectForce); + } + + vendComponent.NextItemToEject = null; + vendComponent.ThrowNextItem = false; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var disabled = EntityQueryEnumerator(); + while (disabled.MoveNext(out var uid, out _, out var comp)) + { + if (comp.NextEmpEject < Timing.CurTime) + { + EjectRandom(uid, true, false, comp); + comp.NextEmpEject += (5 * comp.EjectDelay); + } + } + } + + [SubscribeLocalEvent] + private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args) + { + List priceSets = new(); + + // Find the most expensive inventory and use that as the highest price. + foreach (var vendingInventory in component.CanRestock) + { + double total = 0; + + if (ProtoMan.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype)) + { + foreach (var (item, amount) in inventoryPrototype.StartingInventory) + { + if (ProtoMan.TryIndex(item, out EntityPrototype? entity)) + total += _pricing.GetEstimatedPrice(entity) * amount; + } + } + + priceSets.Add(total); + } + + args.Price += priceSets.Max(); + } + + [SubscribeLocalEvent] + private void OnTryVocalize(Entity ent, ref TryVocalizeEvent args) + { + args.Cancelled |= ent.Comp.Broken; + } } diff --git a/Content.Shared/VendingMachines/SharedVendingMachineSystem.Restock.cs b/Content.Shared/VendingMachines/SharedVendingMachineSystem.Restock.cs index 9b3a1bc29d..963e785458 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineSystem.Restock.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineSystem.Restock.cs @@ -56,6 +56,7 @@ public abstract partial class SharedVendingMachineSystem TryUpdateVisualState((uid, vendComponent)); } + [SubscribeLocalEvent] private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args) { if (args.Target is not { } target || !args.CanReach || args.Handled) @@ -102,6 +103,7 @@ public abstract partial class SharedVendingMachineSystem machineComponent.RestockStream = Audio.PlayPredicted(component.SoundRestockStart, target, args.User)?.Entity; } + [SubscribeLocalEvent] private void OnRestockDoAfter(Entity ent, ref RestockDoAfterEvent args) { if (args.Cancelled) diff --git a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs index 55211a675a..e8360d6e3e 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.DoAfter; using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Emp; -using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Power.EntitySystems; using Content.Shared.UserInterface; @@ -39,15 +38,6 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnVendingGetState); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnRestockDoAfter); - SubscribeLocalEvent(OnActivatableUIOpenAttempt); - SubscribeLocalEvent(OnBreak); - - SubscribeLocalEvent(OnAfterInteract); Subs.BuiEvents(VendingMachineUiKey.Key, subs => { @@ -55,6 +45,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem }); } + [SubscribeLocalEvent] private void OnVendingGetState(Entity entity, ref ComponentGetState args) { var component = entity.Comp; @@ -145,11 +136,13 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem AuthorizedVend(entity.Owner, actor, args.Type, args.ID, entity.Comp); } + [SubscribeLocalEvent] protected virtual void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args) { RestockInventoryFromPrototype(uid, component, component.InitialStockQuality); } + [SubscribeLocalEvent] private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) { if (!ent.Comp.Broken && _receiver.IsPowered(ent.Owner)) @@ -206,6 +199,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem /// The type of inventory the item is from /// The prototype ID of the item /// Whether the item should be thrown in a random direction after ejection + /// /// public void TryEjectVendorItem(EntityUid uid, InventoryType type, string itemId, bool throwItem, EntityUid? user = null, VendingMachineComponent? vendComponent = null) { @@ -333,6 +327,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem Dirty(uid, component); } + [SubscribeLocalEvent] private void OnEmagged(EntityUid uid, VendingMachineComponent component, ref GotEmaggedEvent args) { if (!_emag.CompareFlag(args.Type, EmagType.Interaction)) @@ -429,12 +424,14 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem } } + [SubscribeLocalEvent] private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args) { if (component.Broken) args.Cancel(); } + [SubscribeLocalEvent] private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs) { vendComponent.Broken = true; diff --git a/Content.Shared/VendingMachines/VendingMachineComponent.cs b/Content.Shared/VendingMachines/VendingMachineComponent.cs index 29ff3c654a..84d2da9e4a 100644 --- a/Content.Shared/VendingMachines/VendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/VendingMachineComponent.cs @@ -5,303 +5,299 @@ using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.VendingMachines +namespace Content.Shared.VendingMachines; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] +public sealed partial class VendingMachineComponent : Component { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] - public sealed partial class VendingMachineComponent : Component + /// + /// PrototypeID for the vending machine's inventory, see + /// + // Okay so not using ProtoId here is load-bearing because the ProtoId serializer will log errors if the prototype doesn't exist. + [DataField("pack", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] + public string PackPrototypeId = string.Empty; + + /// + /// Used by the server to determine how long the vending machine stays in the "Deny" state. + /// Used by the client to determine how long the deny animation should be played. + /// + [DataField] + public TimeSpan DenyDelay = TimeSpan.FromSeconds(2); + + /// + /// Used by the server to determine how long the vending machine stays in the "Eject" state. + /// The selected item is dispensed afer this delay. + /// Used by the client to determine how long the deny animation should be played. + /// + [DataField] + public TimeSpan EjectDelay = TimeSpan.FromSeconds(1.2); + + [DataField] + public Dictionary Inventory = new(); + + [DataField] + public Dictionary EmaggedInventory = new(); + + [DataField] + public Dictionary ContrabandInventory = new(); + + /// + /// If true then unlocks the + /// + [DataField] + public bool Contraband; + + [ViewVariables] + public bool Ejecting => EjectEnd != null; + + [ViewVariables] + public bool Denying => DenyEnd != null; + + [ViewVariables] + public bool DispenseOnHitCoolingDown => DispenseOnHitEnd != null; + + [DataField, AutoPausedField] + public TimeSpan? EjectEnd; + + [DataField, AutoPausedField] + public TimeSpan? DenyEnd; + + [DataField] + public TimeSpan? DispenseOnHitEnd; + + public string? NextItemToEject; + + [DataField] + public bool Broken; + + /// + /// When true, will forcefully throw any object it dispenses + /// + [DataField] + public bool CanShoot = false; + + public bool ThrowNextItem = false; + + /// + /// The chance that a vending machine will randomly dispense an item on hit. + /// Chance is 0 if null. + /// + [DataField] + public float? DispenseOnHitChance; + + /// + /// The minimum amount of damage that must be done per hit to have a chance + /// of dispensing an item. + /// + [DataField] + public float? DispenseOnHitThreshold; + + /// + /// Amount of time in seconds that need to pass before damage can cause a vending machine to eject again. + /// This value is separate to because that value might be + /// 0 for a vending machine for legitimate reasons (no desired delay/no eject animation) + /// and can be circumvented with forced ejections. + /// + [DataField] + public TimeSpan? DispenseOnHitCooldown = TimeSpan.FromSeconds(1.0); + + /// + /// Sound that plays when ejecting an item + /// + [DataField] + // Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg + public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg") { - /// - /// PrototypeID for the vending machine's inventory, see - /// - // Okay so not using ProtoId here is load-bearing because the ProtoId serializer will log errors if the prototype doesn't exist. - [DataField("pack", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] - public string PackPrototypeId = string.Empty; - - /// - /// Used by the server to determine how long the vending machine stays in the "Deny" state. - /// Used by the client to determine how long the deny animation should be played. - /// - [DataField] - public TimeSpan DenyDelay = TimeSpan.FromSeconds(2); - - /// - /// Used by the server to determine how long the vending machine stays in the "Eject" state. - /// The selected item is dispensed afer this delay. - /// Used by the client to determine how long the deny animation should be played. - /// - [DataField] - public TimeSpan EjectDelay = TimeSpan.FromSeconds(1.2); - - [DataField] - public Dictionary Inventory = new(); - - [DataField] - public Dictionary EmaggedInventory = new(); - - [DataField] - public Dictionary ContrabandInventory = new(); - - /// - /// If true then unlocks the - /// - [DataField] - public bool Contraband; - - [ViewVariables] - public bool Ejecting => EjectEnd != null; - - [ViewVariables] - public bool Denying => DenyEnd != null; - - [ViewVariables] - public bool DispenseOnHitCoolingDown => DispenseOnHitEnd != null; - - [DataField, AutoPausedField] - public TimeSpan? EjectEnd; - - [DataField, AutoPausedField] - public TimeSpan? DenyEnd; - - [DataField] - public TimeSpan? DispenseOnHitEnd; - - public string? NextItemToEject; - - [DataField] - public bool Broken; - - /// - /// When true, will forcefully throw any object it dispenses - /// - [DataField] - public bool CanShoot = false; - - public bool ThrowNextItem = false; - - /// - /// The chance that a vending machine will randomly dispense an item on hit. - /// Chance is 0 if null. - /// - [DataField] - public float? DispenseOnHitChance; - - /// - /// The minimum amount of damage that must be done per hit to have a chance - /// of dispensing an item. - /// - [DataField] - public float? DispenseOnHitThreshold; - - /// - /// Amount of time in seconds that need to pass before damage can cause a vending machine to eject again. - /// This value is separate to because that value might be - /// 0 for a vending machine for legitimate reasons (no desired delay/no eject animation) - /// and can be circumvented with forced ejections. - /// - [DataField] - public TimeSpan? DispenseOnHitCooldown = TimeSpan.FromSeconds(1.0); - - /// - /// Sound that plays when ejecting an item - /// - [DataField] - // Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg - public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg") + Params = new AudioParams { - Params = new AudioParams - { - Volume = -4f, - Variation = 0.15f - } - }; - - /// - /// Sound that plays when an item can't be ejected - /// - [DataField] - // Yoinked from: https://github.com/discordia-space/CEV-Eris/blob/35bbad6764b14e15c03a816e3e89aa1751660ba9/sound/machines/Custom_deny.ogg - public SoundSpecifier SoundDeny = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); - - public float NonLimitedEjectForce = 7.5f; - - public float NonLimitedEjectRange = 5f; - - /// - /// The quality of the stock in the vending machine on spawn. - /// Represents the percentage chance (0.0f = 0%, 1.0f = 100%) each set of items in the machine is fully-stocked. - /// If not fully stocked, the stock will have a random value between 0 (inclusive) and max stock (exclusive). - /// - [DataField] - public float InitialStockQuality = 1.0f; - - /// - /// While disabled by EMP it randomly ejects items - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan NextEmpEject = TimeSpan.Zero; - - /// - /// Audio entity used during restock in case the doafter gets canceled. - /// - [DataField] - public EntityUid? RestockStream; - - #region Client Visuals - /// - /// RSI state for when the vending machine is unpowered. - /// Will be displayed on the layer - /// - [DataField] - public string? OffState; - - /// - /// RSI state for the screen of the vending machine - /// Will be displayed on the layer - /// - [DataField] - public string? ScreenState; - - /// - /// RSI state for the vending machine's normal state. Usually a looping animation. - /// Will be displayed on the layer - /// - [DataField] - public string? NormalState; - - /// - /// RSI state for the vending machine's eject animation. - /// Will be displayed on the layer - /// - [DataField] - public string? EjectState; - - /// - /// RSI state for the vending machine's deny animation. Will either be played once as sprite flick - /// or looped depending on how is set. - /// Will be displayed on the layer - /// - [DataField] - public string? DenyState; - - /// - /// RSI state for when the vending machine is unpowered. - /// Will be displayed on the layer - /// - [DataField] - public string? BrokenState; - - /// - /// If set to true (default) will loop the animation of the for the duration - /// of . If set to false will play a sprite - /// flick animation for the state and then linger on the final frame until the end of the delay. - /// - [DataField("loopDeny")] - public bool LoopDenyAnimation = true; - #endregion - } - - [Serializable, NetSerializable, DataDefinition] - public sealed partial class VendingMachineInventoryEntry - { - [DataField] - public InventoryType Type; - - [DataField] - public string ID; - - [DataField] - public uint Amount; - - public VendingMachineInventoryEntry(InventoryType type, string id, uint amount) - { - Type = type; - ID = id; - Amount = amount; + Volume = -4f, + Variation = 0.15f } - - public VendingMachineInventoryEntry(VendingMachineInventoryEntry entry) - { - Type = entry.Type; - ID = entry.ID; - Amount = entry.Amount; - } - } - - [Serializable, NetSerializable] - public enum InventoryType : byte - { - Regular, - Emagged, - Contraband - } - - [Serializable, NetSerializable] - public enum VendingMachineVisuals : byte - { - VisualState - } - - [Serializable, NetSerializable] - public enum VendingMachineVisualState : byte - { - Normal, - Off, - Broken, - Eject, - Deny, - } - - public enum VendingMachineVisualLayers : byte - { - /// - /// Off / Broken. The other layers will overlay this if the machine is on. - /// - Base, - /// - /// Normal / Deny / Eject - /// - BaseUnshaded, - /// - /// Screens that are persistent (where the machine is not off or broken) - /// - Screen - } - - [Serializable, NetSerializable] - public enum ContrabandWireKey : byte - { - StatusKey, - TimeoutKey - } - - [Serializable, NetSerializable] - public enum EjectWireKey : byte - { - StatusKey, - } - - public sealed partial class VendingMachineSelfDispenseEvent : InstantActionEvent - { - }; - [Serializable, NetSerializable] - public sealed class VendingMachineComponentState : ComponentState + /// + /// Sound that plays when an item can't be ejected + /// + [DataField] + // Yoinked from: https://github.com/discordia-space/CEV-Eris/blob/35bbad6764b14e15c03a816e3e89aa1751660ba9/sound/machines/Custom_deny.ogg + public SoundSpecifier SoundDeny = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + + public float NonLimitedEjectForce = 7.5f; + + public float NonLimitedEjectRange = 5f; + + /// + /// The quality of the stock in the vending machine on spawn. + /// Represents the percentage chance (0.0f = 0%, 1.0f = 100%) each set of items in the machine is fully-stocked. + /// If not fully stocked, the stock will have a random value between 0 (inclusive) and max stock (exclusive). + /// + [DataField] + public float InitialStockQuality = 1.0f; + + /// + /// While disabled by EMP it randomly ejects items + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextEmpEject = TimeSpan.Zero; + + /// + /// Audio entity used during restock in case the doafter gets canceled. + /// + [DataField] + public EntityUid? RestockStream; + + #region Client Visuals + /// + /// RSI state for when the vending machine is unpowered. + /// Will be displayed on the layer + /// + [DataField] + public string? OffState; + + /// + /// RSI state for the screen of the vending machine + /// Will be displayed on the layer + /// + [DataField] + public string? ScreenState; + + /// + /// RSI state for the vending machine's normal state. Usually a looping animation. + /// Will be displayed on the layer + /// + [DataField] + public string? NormalState; + + /// + /// RSI state for the vending machine's eject animation. + /// Will be displayed on the layer + /// + [DataField] + public string? EjectState; + + /// + /// RSI state for the vending machine's deny animation. Will either be played once as sprite flick + /// or looped depending on how is set. + /// Will be displayed on the layer + /// + [DataField] + public string? DenyState; + + /// + /// RSI state for when the vending machine is unpowered. + /// Will be displayed on the layer + /// + [DataField] + public string? BrokenState; + + /// + /// If set to true (default) will loop the animation of the for the duration + /// of . If set to false will play a sprite + /// flick animation for the state and then linger on the final frame until the end of the delay. + /// + [DataField("loopDeny")] + public bool LoopDenyAnimation = true; + #endregion +} + +[Serializable, NetSerializable, DataDefinition] +public sealed partial class VendingMachineInventoryEntry +{ + [DataField] + public InventoryType Type; + + [DataField] + public string ID; + + [DataField] + public uint Amount; + + public VendingMachineInventoryEntry(InventoryType type, string id, uint amount) { - public Dictionary Inventory = new(); + Type = type; + ID = id; + Amount = amount; + } - public Dictionary EmaggedInventory = new(); - - public Dictionary ContrabandInventory = new(); - - public bool Contraband; - - public TimeSpan? EjectEnd; - - public TimeSpan? DenyEnd; - - public TimeSpan? DispenseOnHitEnd; - - public bool Broken; + public VendingMachineInventoryEntry(VendingMachineInventoryEntry entry) + { + Type = entry.Type; + ID = entry.ID; + Amount = entry.Amount; } } + +[Serializable, NetSerializable] +public enum InventoryType : byte +{ + Regular, + Emagged, + Contraband +} + +[Serializable, NetSerializable] +public enum VendingMachineVisuals : byte +{ + VisualState +} + +[Serializable, NetSerializable] +public enum VendingMachineVisualState : byte +{ + Normal, + Off, + Broken, + Eject, + Deny +} + +public enum VendingMachineVisualLayers : byte +{ + /// + /// Off / Broken. The other layers will overlay this if the machine is on. + /// + Base, + /// + /// Normal / Deny / Eject + /// + BaseUnshaded, + /// + /// Screens that are persistent (where the machine is not off or broken) + /// + Screen +} + +[Serializable, NetSerializable] +public enum ContrabandWireKey : byte +{ + StatusKey, + TimeoutKey +} + +[Serializable, NetSerializable] +public enum EjectWireKey : byte +{ + StatusKey +} + +public sealed partial class VendingMachineSelfDispenseEvent : InstantActionEvent; + +[Serializable, NetSerializable] +public sealed class VendingMachineComponentState : ComponentState +{ + public Dictionary Inventory = new(); + + public Dictionary EmaggedInventory = new(); + + public Dictionary ContrabandInventory = new(); + + public bool Contraband; + + public TimeSpan? EjectEnd; + + public TimeSpan? DenyEnd; + + public TimeSpan? DispenseOnHitEnd; + + public bool Broken; +} diff --git a/Content.Shared/VendingMachines/VendingMachineInterfaceState.cs b/Content.Shared/VendingMachines/VendingMachineInterfaceState.cs index 27651bb1cb..c712681a80 100644 --- a/Content.Shared/VendingMachines/VendingMachineInterfaceState.cs +++ b/Content.Shared/VendingMachines/VendingMachineInterfaceState.cs @@ -1,22 +1,16 @@ using Robust.Shared.Serialization; -namespace Content.Shared.VendingMachines -{ - [Serializable, NetSerializable] - public sealed class VendingMachineEjectMessage : BoundUserInterfaceMessage - { - public readonly InventoryType Type; - public readonly string ID; - public VendingMachineEjectMessage(InventoryType type, string id) - { - Type = type; - ID = id; - } - } +namespace Content.Shared.VendingMachines; - [Serializable, NetSerializable] - public enum VendingMachineUiKey - { - Key, - } +[Serializable, NetSerializable] +public sealed class VendingMachineEjectMessage(InventoryType type, string id) : BoundUserInterfaceMessage +{ + public readonly InventoryType Type = type; + public readonly string ID = id; +} + +[Serializable, NetSerializable] +public enum VendingMachineUiKey +{ + Key } diff --git a/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs b/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs index be4816b319..ade14d60fb 100644 --- a/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs +++ b/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs @@ -1,23 +1,21 @@ using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -namespace Content.Shared.VendingMachines +namespace Content.Shared.VendingMachines; + +[Prototype] +public sealed partial class VendingMachineInventoryPrototype : IPrototype { - [Prototype] - public sealed partial class VendingMachineInventoryPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; - [DataField("startingInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] - public Dictionary StartingInventory { get; private set; } = new(); + [DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + public Dictionary StartingInventory { get; private set; } = new(); - [DataField("emaggedInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] - public Dictionary? EmaggedInventory { get; private set; } + [DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + public Dictionary? EmaggedInventory { get; private set; } - [DataField("contrabandInventory", customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] - public Dictionary? ContrabandInventory { get; private set; } - } + [DataField(customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + public Dictionary? ContrabandInventory { get; private set; } }