From b837b186a5ccca251ac135465674de7a00da2809 Mon Sep 17 00:00:00 2001 From: Fishfish458 <47410468+Fishfish458@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:16:41 -0600 Subject: [PATCH] Attempted Small Vending Machine Refactor (#6157) Co-authored-by: fishfish458 --- .../UI/VendingMachineMenu.xaml.cs | 2 +- .../VendingMachineComponent.cs | 270 ++-------------- .../VendingMachines/VendingMachineSystem.cs | 287 ++++++++++++++++++ .../SharedVendingMachineComponent.cs | 8 + 4 files changed, 319 insertions(+), 248 deletions(-) create mode 100644 Content.Server/VendingMachines/VendingMachineSystem.cs diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs index e32ad58194..f0513b5133 100644 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs +++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs @@ -51,7 +51,7 @@ namespace Content.Client.VendingMachines.UI } SetSize = (Math.Clamp((longestEntry.Length + 2) * 12, 250, 300), - Math.Clamp(VendingContents.Count * 30, 150, 350)); + Math.Clamp(VendingContents.Count * 50, 150, 350)); } public void ItemSelected(ItemList.ItemListSelectedEventArgs args) diff --git a/Content.Server/VendingMachines/VendingMachineComponent.cs b/Content.Server/VendingMachines/VendingMachineComponent.cs index ce7c993a0d..b810595941 100644 --- a/Content.Server/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/VendingMachines/VendingMachineComponent.cs @@ -1,284 +1,61 @@ using System; -using System.Collections.Generic; -using System.Linq; -using Content.Server.Popups; -using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Server.WireHacking; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Acts; -using Content.Shared.Interaction; using Content.Shared.Sound; using Content.Shared.VendingMachines; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; +using Content.Server.VendingMachines.systems; using static Content.Shared.Wires.SharedWiresComponent; namespace Content.Server.VendingMachines { [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class VendingMachineComponent : SharedVendingMachineComponent, IActivate, IBreakAct, IWires + public class VendingMachineComponent : SharedVendingMachineComponent, IWires { - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - private bool _ejecting; - private TimeSpan _animationDuration = TimeSpan.Zero; + public bool Ejecting; + public TimeSpan AnimationDuration = TimeSpan.Zero; [DataField("pack")] - private string _packPrototypeId = string.Empty; - private string _spriteName = ""; - - private bool Powered => !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; - private bool _broken; - + public string PackPrototypeId = string.Empty; + public string SpriteName = ""; + public bool Broken; + /// + /// When true, will forcefully throw any object it dispenses + /// + [DataField("speedLimiter")] + public bool CanShoot = false; [DataField("soundVend")] // Grabbed from: https://github.com/discordia-space/CEV-Eris/blob/f702afa271136d093ddeb415423240a2ceb212f0/sound/machines/vending_drop.ogg - private SoundSpecifier _soundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg"); + public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg"); [DataField("soundDeny")] // Yoinked from: https://github.com/discordia-space/CEV-Eris/blob/35bbad6764b14e15c03a816e3e89aa1751660ba9/sound/machines/Custom_deny.ogg - private SoundSpecifier _soundDeny = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); - - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key); - - public bool Broken => _broken; - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if(!_entMan.TryGetComponent(eventArgs.User, out ActorComponent? actor)) - { - return; - } - if (!Powered) - return; - - var wires = _entMan.GetComponent(Owner); - if (wires.IsPanelOpen) - { - wires.OpenInterface(actor.PlayerSession); - } else - { - UserInterface?.Toggle(actor.PlayerSession); - } - } - - private void InitializeFromPrototype() - { - if (string.IsNullOrEmpty(_packPrototypeId)) { return; } - if (!_prototypeManager.TryIndex(_packPrototypeId, out VendingMachineInventoryPrototype? packPrototype)) - { - return; - } - - _entMan.GetComponent(Owner).EntityName = packPrototype.Name; - _animationDuration = TimeSpan.FromSeconds(packPrototype.AnimationDuration); - _spriteName = packPrototype.SpriteName; - if (!string.IsNullOrEmpty(_spriteName)) - { - var spriteComponent = _entMan.GetComponent(Owner); - const string vendingMachineRSIPath = "Structures/Machines/VendingMachines/{0}.rsi"; - spriteComponent.BaseRSIPath = string.Format(vendingMachineRSIPath, _spriteName); - } - - var inventory = new List(); - foreach(var (id, amount) in packPrototype.StartingInventory) - { - inventory.Add(new VendingMachineInventoryEntry(id, amount)); - } - Inventory = inventory; - } - - protected override void Initialize() - { - base.Initialize(); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } - - if (_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver)) - { - TrySetVisualState(receiver.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off); - } - - InitializeFromPrototype(); - } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleMessage(ComponentMessage message, IComponent? component) - { -#pragma warning disable 618 - base.HandleMessage(message, component); -#pragma warning restore 618 - switch (message) - { - case PowerChangedMessage powerChanged: - UpdatePower(powerChanged); - break; - } - } - - private void UpdatePower(PowerChangedMessage args) - { - var state = args.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off; - TrySetVisualState(state); - } - - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) - { - if (!Powered) - return; - - var message = serverMsg.Message; - switch (message) - { - case VendingMachineEjectMessage msg: - TryEject(msg.ID, serverMsg.Session.AttachedEntity); - break; - case InventorySyncRequestMessage _: - UserInterface?.SendMessage(new VendingMachineInventoryMessage(Inventory)); - break; - } - } - - private void TryEject(string id) - { - if (_ejecting || _broken) - { - return; - } - - var entry = Inventory.Find(x => x.ID == id); - if (entry == null) - { - Owner.PopupMessageEveryone(Loc.GetString("vending-machine-component-try-eject-invalid-item")); - Deny(); - return; - } - - if (entry.Amount <= 0) - { - Owner.PopupMessageEveryone(Loc.GetString("vending-machine-component-try-eject-out-of-stock")); - Deny(); - return; - } - - _ejecting = true; - entry.Amount--; - UserInterface?.SendMessage(new VendingMachineInventoryMessage(Inventory)); - TrySetVisualState(VendingMachineVisualState.Eject); - - Owner.SpawnTimer(_animationDuration, () => - { - _ejecting = false; - TrySetVisualState(VendingMachineVisualState.Normal); - _entMan.SpawnEntity(id, _entMan.GetComponent(Owner).Coordinates); - }); - - SoundSystem.Play(Filter.Pvs(Owner), _soundVend.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); - } - - private void TryEject(string id, EntityUid? sender) - { - if (_entMan.TryGetComponent(Owner, out var accessReader)) - { - var accessSystem = EntitySystem.Get(); - if (sender == null || !accessSystem.IsAllowed(accessReader, sender.Value)) - { - Owner.PopupMessageEveryone(Loc.GetString("vending-machine-component-try-eject-access-denied")); - Deny(); - return; - } - } - TryEject(id); - } - - private void Deny() - { - SoundSystem.Play(Filter.Pvs(Owner), _soundDeny.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); - - // Play the Deny animation - TrySetVisualState(VendingMachineVisualState.Deny); - //TODO: This duration should be a distinct value specific to the deny animation - Owner.SpawnTimer(_animationDuration, () => - { - TrySetVisualState(VendingMachineVisualState.Normal); - }); - } - - private void TrySetVisualState(VendingMachineVisualState state) - { - var finalState = state; - if (_broken) - { - finalState = VendingMachineVisualState.Broken; - } - else if (_ejecting) - { - finalState = VendingMachineVisualState.Eject; - } - else if (!Powered) - { - finalState = VendingMachineVisualState.Off; - } - - if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance)) - { - appearance.SetData(VendingMachineVisuals.VisualState, finalState); - } - } - - public void OnBreak(BreakageEventArgs eventArgs) - { - _broken = true; - TrySetVisualState(VendingMachineVisualState.Broken); - } + public SoundSpecifier SoundDeny = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); + [ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key); + public float NonLimitedEjectForce = 7.5f; + public float NonLimitedEjectRange = 5f; public enum Wires { /// /// Shoots a random item when pulsed. /// - Shoot + Limiter } - - void IWires.RegisterWires(WiresComponent.WiresBuilder builder) + public void RegisterWires(WiresComponent.WiresBuilder builder) { - builder.CreateWire(Wires.Shoot); + builder.CreateWire(Wires.Limiter); } - void IWires.WiresUpdate(WiresUpdateEventArgs args) + public void WiresUpdate(WiresUpdateEventArgs args) { var identifier = (Wires) args.Identifier; - if (identifier == Wires.Shoot && args.Action == WiresAction.Pulse) + if (identifier == Wires.Limiter && args.Action == WiresAction.Pulse) { - EjectRandom(); + EntitySystem.Get().EjectRandom(this.Owner, true, this); } } - - /// - /// Ejects a random item if present. - /// - private void EjectRandom() - { - var availableItems = Inventory.Where(x => x.Amount > 0).ToList(); - if (availableItems.Count <= 0) - { - return; - } - TryEject(_random.Pick(availableItems).ID); - } } public class WiresUpdateEventArgs : EventArgs @@ -297,7 +74,6 @@ namespace Content.Server.VendingMachines { void RegisterWires(WiresComponent.WiresBuilder builder); void WiresUpdate(WiresUpdateEventArgs args); - } } diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs new file mode 100644 index 0000000000..1cb63d1e32 --- /dev/null +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -0,0 +1,287 @@ +using System.Collections.Generic; +using Content.Shared.Interaction; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Player; +using System; +using System.Linq; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.WireHacking; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.VendingMachines; +using Robust.Server.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Content.Server.Throwing; +using Robust.Shared.Maths; +using Content.Shared.Acts; +using static Content.Shared.VendingMachines.SharedVendingMachineComponent; + +namespace Content.Server.VendingMachines.systems +{ + public sealed class VendingMachineSystem : EntitySystem + { + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(HandleActivate); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnInventoryRequestMessage); + SubscribeLocalEvent(OnInventoryEjectMessage); + SubscribeLocalEvent(OnBreak); + } + + private void OnComponentInit(EntityUid uid, VendingMachineComponent component, ComponentInit args) + { + base.Initialize(); + + if (TryComp(component.Owner, out var receiver)) + { + TryUpdateVisualState(uid, null, component); + } + + InitializeFromPrototype(uid, component); + } + + private void OnInventoryRequestMessage(EntityUid uid, VendingMachineComponent component, InventorySyncRequestMessage args) + { + if (!IsPowered(uid, component)) + return; + + component.UserInterface?.SendMessage(new VendingMachineInventoryMessage(component.Inventory)); + } + + private void OnInventoryEjectMessage(EntityUid uid, VendingMachineComponent component, VendingMachineEjectMessage args) + { + if (!IsPowered(uid, component)) + return; + + if (args.Session.AttachedEntity is not { Valid: true } entity || Deleted(entity)) + return; + + AuthorizedVend(uid, entity, args.ID, component); + } + + private void HandleActivate(EntityUid uid, VendingMachineComponent component, ActivateInWorldEvent args) + { + if (!TryComp(args.User, out var actor)) + { + return; + } + + if (!IsPowered(uid, component)) + { + return; + } + + if (TryComp(uid, out var wires)) + { + if (wires.IsPanelOpen) + { + wires.OpenInterface(actor.PlayerSession); + return; + } + } + + component.UserInterface?.Toggle(actor.PlayerSession); + } + + private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, PowerChangedEvent args) + { + TryUpdateVisualState(uid, null, component); + } + + private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs) + { + vendComponent.Broken = true; + TryUpdateVisualState(uid, VendingMachineVisualState.Broken, vendComponent); + } + + public bool IsPowered(EntityUid uid, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return false; + + if (!TryComp(vendComponent.Owner, out var receiver)) + { + return false; + } + return receiver.Powered; + } + + public void InitializeFromPrototype(EntityUid uid, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + if (string.IsNullOrEmpty(vendComponent.PackPrototypeId)) { return; } + + if (!_prototypeManager.TryIndex(vendComponent.PackPrototypeId, out VendingMachineInventoryPrototype? packPrototype)) + { + return; + } + + MetaData(uid).EntityName = packPrototype.Name; + vendComponent.AnimationDuration = TimeSpan.FromSeconds(packPrototype.AnimationDuration); + vendComponent.SpriteName = packPrototype.SpriteName; + if (!string.IsNullOrEmpty(vendComponent.SpriteName)) + { + if (TryComp(vendComponent.Owner, out var spriteComp)) { + const string vendingMachineRSIPath = "Structures/Machines/VendingMachines/{0}.rsi"; + spriteComp.BaseRSIPath = string.Format(vendingMachineRSIPath, vendComponent.SpriteName); + } + } + var inventory = new List(); + foreach (var (id, amount) in packPrototype.StartingInventory) + { + if (!_prototypeManager.TryIndex(id, out EntityPrototype? prototype)) + { + continue; + } + inventory.Add(new VendingMachineInventoryEntry(id, amount)); + } + vendComponent.Inventory = inventory; + } + + public void Deny(EntityUid uid, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + SoundSystem.Play(Filter.Pvs(vendComponent.Owner), vendComponent.SoundDeny.GetSound(), vendComponent.Owner, AudioParams.Default.WithVolume(-2f)); + // Play the Deny animation + TryUpdateVisualState(uid, VendingMachineVisualState.Deny, vendComponent); + //TODO: This duration should be a distinct value specific to the deny animation + vendComponent.Owner.SpawnTimer(vendComponent.AnimationDuration, () => + { + TryUpdateVisualState(uid, VendingMachineVisualState.Normal, vendComponent); + }); + } + + public bool IsAuthorized(EntityUid uid, EntityUid? sender, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return false; + + if (TryComp(vendComponent.Owner, out var accessReader)) + { + if (sender == null || !_accessReader.IsAllowed(accessReader, sender.Value)) + { + _popupSystem.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), uid, Filter.Pvs(uid)); + Deny(uid, vendComponent); + return false; + } + } + return true; + } + + public void TryEjectVendorItem(EntityUid uid, string itemId, bool throwItem, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + if (vendComponent.Ejecting || vendComponent.Broken || !IsPowered(uid, vendComponent)) + { + return; + } + + var entry = vendComponent.Inventory.Find(x => x.ID == itemId); + if (entry == null) + { + _popupSystem.PopupEntity(Loc.GetString("vending-machine-component-try-eject-invalid-item"), uid, Filter.Pvs(uid)); + Deny(uid, vendComponent); + return; + } + + if (entry.Amount <= 0) + { + _popupSystem.PopupEntity(Loc.GetString("vending-machine-component-try-eject-out-of-stock"), uid, Filter.Pvs(uid)); + Deny(uid, vendComponent); + return; + } + + if (entry.ID == null) + return; + + if (!TryComp(vendComponent.Owner, out var transformComp)) + return; + + // Start Ejecting, and prevent users from ordering while anim playing + vendComponent.Ejecting = true; + entry.Amount--; + vendComponent.UserInterface?.SendMessage(new VendingMachineInventoryMessage(vendComponent.Inventory)); + TryUpdateVisualState(uid, VendingMachineVisualState.Eject, vendComponent); + vendComponent.Owner.SpawnTimer(vendComponent.AnimationDuration, () => + { + vendComponent.Ejecting = false; + TryUpdateVisualState(uid, VendingMachineVisualState.Normal, vendComponent); + var ent = EntityManager.SpawnEntity(entry.ID, transformComp.Coordinates); + if (throwItem) + { + float range = vendComponent.NonLimitedEjectRange; + Vector2 direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range)); + ent.TryThrow(direction, vendComponent.NonLimitedEjectForce); + } + }); + SoundSystem.Play(Filter.Pvs(vendComponent.Owner), vendComponent.SoundVend.GetSound(), vendComponent.Owner, AudioParams.Default.WithVolume(-2f)); + } + + public void AuthorizedVend(EntityUid uid, EntityUid sender, string itemId, VendingMachineComponent component) + { + if (IsAuthorized(uid, sender, component)) + { + TryEjectVendorItem(uid, itemId, component.CanShoot, component); + } + return; + } + + public void TryUpdateVisualState(EntityUid uid, VendingMachineVisualState? state = VendingMachineVisualState.Normal, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + var finalState = state == null ? VendingMachineVisualState.Normal : state; + if (vendComponent.Broken) + { + finalState = VendingMachineVisualState.Broken; + } + else if (vendComponent.Ejecting) + { + finalState = VendingMachineVisualState.Eject; + } + else if (!IsPowered(uid, vendComponent)) + { + finalState = VendingMachineVisualState.Off; + } + + if (TryComp(vendComponent.Owner, out var appearance)) + { + appearance.SetData(VendingMachineVisuals.VisualState, finalState); + } + } + + public void EjectRandom(EntityUid uid, bool throwItem, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + var availableItems = vendComponent.Inventory.Where(x => x.Amount > 0).ToList(); + if (availableItems.Count <= 0) + { + return; + } + + TryEjectVendorItem(uid, _random.Pick(availableItems).ID, throwItem, vendComponent); + } + } +} diff --git a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs index 407ba232e7..1827986019 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs @@ -75,5 +75,13 @@ namespace Content.Shared.VendingMachines Amount = amount; } } + [Serializable, NetSerializable] + public enum VendingMachineWireStatus : byte + { + Power, + Access, + Advertisement, + Limiter + } } }