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
+ }
}
}