diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs index 957bac5ee9..47a0061d73 100644 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs +++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml.cs @@ -56,7 +56,7 @@ namespace Content.Client.VendingMachines.UI public void ItemSelected(ItemList.ItemListSelectedEventArgs args) { - Owner.Eject(_cachedInventory[args.ItemIndex].ID); + Owner.Eject(_cachedInventory[args.ItemIndex].Type, _cachedInventory[args.ItemIndex].ID); } } } diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs index 99687769ef..3902bfec23 100644 --- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs +++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs @@ -32,15 +32,15 @@ namespace Content.Client.VendingMachines VendingMachine = vendingMachine; _menu = new VendingMachineMenu(this) {Title = entMan.GetComponent(Owner.Owner).EntityName}; - _menu.Populate(VendingMachine.Inventory); + _menu.Populate(VendingMachine.AllInventory); _menu.OnClose += Close; _menu.OpenCentered(); } - public void Eject(string id) + public void Eject(InventoryType type, string id) { - SendMessage(new VendingMachineEjectMessage(id)); + SendMessage(new VendingMachineEjectMessage(type, id)); } protected override void ReceiveMessage(BoundUserInterfaceMessage message) diff --git a/Content.Server/VendingMachines/VendingMachineComponent.cs b/Content.Server/VendingMachines/VendingMachineComponent.cs index b52f9fbb1a..07dc2e6d25 100644 --- a/Content.Server/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/VendingMachines/VendingMachineComponent.cs @@ -7,7 +7,7 @@ using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; -using Content.Server.VendingMachines.systems; +using Content.Server.VendingMachines.Systems; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.VendingMachines @@ -16,25 +16,34 @@ namespace Content.Server.VendingMachines public sealed class VendingMachineComponent : SharedVendingMachineComponent { public bool Ejecting; - public bool Emagged = false; + public TimeSpan AnimationDuration = TimeSpan.Zero; - [ViewVariables] [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer))] public string PackPrototypeId = string.Empty; - [ViewVariables] [DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer))] public string EmagPackPrototypeId = string.Empty; + + [ViewVariables] [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer))] + 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 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 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; } } diff --git a/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs b/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs new file mode 100644 index 0000000000..0e2100f6bd --- /dev/null +++ b/Content.Server/VendingMachines/VendingMachineContrabandWireAction.cs @@ -0,0 +1,43 @@ +using Content.Server.Wires; +using Content.Shared.VendingMachines; +using Content.Shared.Wires; + +namespace Content.Server.VendingMachines; + +[DataDefinition] +public sealed class VendingMachineContrabandWireAction : BaseToggleWireAction +{ + private readonly Color _color = Color.Green; + private readonly string _text = "MNGR"; + public override object? StatusKey { get; } = ContrabandWireKey.StatusKey; + public override object? TimeoutKey { get; } = ContrabandWireKey.TimeoutKey; + + public override StatusLightData? GetStatusLightData(Wire wire) + { + var lightState = StatusLightState.Off; + if (IsPowered(wire.Owner) && EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent vending)) + { + lightState = vending.Contraband + ? StatusLightState.BlinkingSlow + : StatusLightState.On; + } + + return new StatusLightData( + _color, + lightState, + _text); + } + + public override void ToggleValue(EntityUid owner, bool setting) + { + if (EntityManager.TryGetComponent(owner, out VendingMachineComponent vending)) + { + vending.Contraband = !setting; + } + } + + public override bool GetValue(EntityUid owner) + { + return EntityManager.TryGetComponent(owner, out VendingMachineComponent vending) && !vending.Contraband; + } +} diff --git a/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs b/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs new file mode 100644 index 0000000000..c6bf752267 --- /dev/null +++ b/Content.Server/VendingMachines/VendingMachineEjectItemWireAction.cs @@ -0,0 +1,68 @@ +using Content.Server.VendingMachines.Systems; +using Content.Server.Wires; +using Content.Shared.VendingMachines; +using Content.Shared.Wires; + +namespace Content.Server.VendingMachines; + +[DataDefinition] +public sealed class VendingMachineEjectItemWireAction : BaseWireAction +{ + private VendingMachineSystem _vendingMachineSystem = default!; + + private Color _color = Color.Red; + private string _text = "VEND"; + public override object? StatusKey { get; } = EjectWireKey.StatusKey; + + public override StatusLightData? GetStatusLightData(Wire wire) + { + var lightState = StatusLightState.Off; + + if (IsPowered(wire.Owner) + && EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent vending)) + { + lightState = vending.CanShoot + ? StatusLightState.BlinkingFast + : StatusLightState.On; + } + + return new StatusLightData( + _color, + lightState, + _text); + } + + public override void Initialize() + { + base.Initialize(); + + _vendingMachineSystem = EntitySystem.Get(); + } + + public override bool Cut(EntityUid user, Wire wire) + { + if (EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent vending)) + { + vending.CanShoot = true; + } + + return true; + } + + public override bool Mend(EntityUid user, Wire wire) + { + if (EntityManager.TryGetComponent(wire.Owner, out VendingMachineComponent vending)) + { + vending.CanShoot = false; + } + + return true; + } + + public override bool Pulse(EntityUid user, Wire wire) + { + _vendingMachineSystem.EjectRandom(wire.Owner, true); + + return true; + } +} diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 15acb8ec20..b2c404533a 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -14,7 +14,7 @@ using Content.Shared.Emag.Systems; using static Content.Shared.VendingMachines.SharedVendingMachineComponent; using Content.Shared.Throwing; -namespace Content.Server.VendingMachines.systems +namespace Content.Server.VendingMachines.Systems { public sealed class VendingMachineSystem : EntitySystem { @@ -52,7 +52,12 @@ namespace Content.Server.VendingMachines.systems if (!IsPowered(uid, component)) return; - component.UserInterface?.SendMessage(new VendingMachineInventoryMessage(component.Inventory)); + var inventory = new List(component.Inventory); + + if (component.Emagged) inventory.AddRange(component.EmaggedInventory); + if (component.Contraband) inventory.AddRange(component.ContrabandInventory); + + component.UserInterface?.SendMessage(new VendingMachineInventoryMessage(inventory)); } private void OnInventoryEjectMessage(EntityUid uid, VendingMachineComponent component, VendingMachineEjectMessage args) @@ -63,7 +68,7 @@ namespace Content.Server.VendingMachines.systems if (args.Session.AttachedEntity is not { Valid: true } entity || Deleted(entity)) return; - AuthorizedVend(uid, entity, args.ID, component); + AuthorizedVend(uid, entity, args.Type, args.ID, component); } private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, PowerChangedEvent args) @@ -79,10 +84,9 @@ namespace Content.Server.VendingMachines.systems private void OnEmagged(EntityUid uid, VendingMachineComponent component, GotEmaggedEvent args) { - if (component.Emagged || component.EmagPackPrototypeId == string.Empty) + if (component.Emagged || component.EmaggedInventory.Count == 0 ) return; - AddVendEntries(component, component.EmagPackPrototypeId); component.Emagged = true; args.Handled = true; } @@ -121,36 +125,42 @@ namespace Content.Server.VendingMachines.systems 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; + + AddInventoryFromPrototype(uid, packPrototype.StartingInventory, InventoryType.Regular, vendComponent); + AddInventoryFromPrototype(uid, packPrototype.EmaggedInventory, InventoryType.Emagged, vendComponent); + AddInventoryFromPrototype(uid, packPrototype.ContrabandInventory, InventoryType.Contraband, vendComponent); } - /// - /// Add more entries for any reason AFTER initialization (emag, machine upgrades, etc) - /// - public void AddVendEntries(VendingMachineComponent component, string pack) + private void AddInventoryFromPrototype(EntityUid uid, Dictionary? entries, + InventoryType type, + VendingMachineComponent? component = null) { - if (!_prototypeManager.TryIndex(pack, out VendingMachineInventoryPrototype? packPrototype)) + if (!Resolve(uid, ref component) || entries == null) { - Logger.Error($"Pack has no valid inventory prototype: {pack}"); return; } - foreach (var (id, amount) in packPrototype.StartingInventory) + var inventory = new List(); + + foreach (var (id, amount) in entries) { - if (!_prototypeManager.TryIndex(id, out EntityPrototype? prototype)) + if (_prototypeManager.HasIndex(id)) { - continue; + inventory.Add(new VendingMachineInventoryEntry(type, id, amount)); } - component.Inventory.Add(new VendingMachineInventoryEntry(id, amount)); + } + + switch (type) + { + case InventoryType.Regular: + component.Inventory.AddRange(inventory); + break; + case InventoryType.Emagged: + component.EmaggedInventory.AddRange(inventory); + break; + case InventoryType.Contraband: + component.ContrabandInventory.AddRange(inventory); + break; } } @@ -171,12 +181,12 @@ namespace Content.Server.VendingMachines.systems public bool IsAuthorized(EntityUid uid, EntityUid? sender, VendingMachineComponent? vendComponent = null) { - if (!Resolve(uid, ref vendComponent)) + if (!Resolve(uid, ref vendComponent) || sender == null) return false; if (TryComp(vendComponent.Owner, out var accessReader)) { - if (sender == null || !_accessReader.IsAllowed(accessReader, sender.Value)) + if (!_accessReader.IsAllowed(accessReader, sender.Value) && !vendComponent.Emagged) { _popupSystem.PopupEntity(Loc.GetString("vending-machine-component-try-eject-access-denied"), uid, Filter.Pvs(uid)); Deny(uid, vendComponent); @@ -186,7 +196,7 @@ namespace Content.Server.VendingMachines.systems return true; } - public void TryEjectVendorItem(EntityUid uid, string itemId, bool throwItem, VendingMachineComponent? vendComponent = null) + public void TryEjectVendorItem(EntityUid uid, InventoryType type, string itemId, bool throwItem, VendingMachineComponent? vendComponent = null) { if (!Resolve(uid, ref vendComponent)) return; @@ -196,7 +206,14 @@ namespace Content.Server.VendingMachines.systems return; } - var entry = vendComponent.Inventory.Find(x => x.ID == itemId); + var entry = type switch + { + InventoryType.Regular => vendComponent.Inventory.Find(x => x.ID == itemId), + InventoryType.Emagged when vendComponent.Emagged => vendComponent.EmaggedInventory.Find(x => x.ID == itemId), + InventoryType.Contraband when vendComponent.Contraband => vendComponent.ContrabandInventory.Find(x => x.ID == itemId), + _ => null + }; + if (entry == null) { _popupSystem.PopupEntity(Loc.GetString("vending-machine-component-try-eject-invalid-item"), uid, Filter.Pvs(uid)); @@ -211,7 +228,7 @@ namespace Content.Server.VendingMachines.systems return; } - if (entry.ID == null) + if (string.IsNullOrEmpty(entry.ID)) return; if (!TryComp(vendComponent.Owner, out var transformComp)) @@ -220,7 +237,7 @@ namespace Content.Server.VendingMachines.systems // Start Ejecting, and prevent users from ordering while anim playing vendComponent.Ejecting = true; entry.Amount--; - vendComponent.UserInterface?.SendMessage(new VendingMachineInventoryMessage(vendComponent.Inventory)); + vendComponent.UserInterface?.SendMessage(new VendingMachineInventoryMessage(vendComponent.AllInventory)); TryUpdateVisualState(uid, VendingMachineVisualState.Eject, vendComponent); vendComponent.Owner.SpawnTimer(vendComponent.AnimationDuration, () => { @@ -237,13 +254,12 @@ namespace Content.Server.VendingMachines.systems 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) + public void AuthorizedVend(EntityUid uid, EntityUid sender, InventoryType type, string itemId, VendingMachineComponent component) { if (IsAuthorized(uid, sender, component)) { - TryEjectVendorItem(uid, itemId, component.CanShoot, component); + TryEjectVendorItem(uid, type, itemId, component.CanShoot, component); } - return; } public void TryUpdateVisualState(EntityUid uid, VendingMachineVisualState? state = VendingMachineVisualState.Normal, VendingMachineComponent? vendComponent = null) @@ -276,13 +292,14 @@ namespace Content.Server.VendingMachines.systems if (!Resolve(uid, ref vendComponent)) return; - var availableItems = vendComponent.Inventory.Where(x => x.Amount > 0).ToList(); + var availableItems = vendComponent.AllInventory.Where(x => x.Amount > 0).ToList(); if (availableItems.Count <= 0) { return; } - TryEjectVendorItem(uid, _random.Pick(availableItems).ID, throwItem, vendComponent); + var item = _random.Pick(availableItems); + TryEjectVendorItem(uid, item.Type, item.ID, throwItem, vendComponent); } } } diff --git a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs index 6633736f3d..b6eadb3254 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs @@ -11,8 +11,25 @@ namespace Content.Shared.VendingMachines [NetworkedComponent()] public class SharedVendingMachineComponent : Component { - [ViewVariables] - public List Inventory = new(); + [ViewVariables] public List Inventory = new(); + [ViewVariables] public List EmaggedInventory = new(); + [ViewVariables] public List ContrabandInventory = new(); + + public List AllInventory + { + get + { + var inventory = new List(Inventory); + + if (Emagged) inventory.AddRange(EmaggedInventory); + if (Contraband) inventory.AddRange(ContrabandInventory); + + return inventory; + } + } + + public bool Emagged; + public bool Contraband; [Serializable, NetSerializable] public enum VendingMachineVisuals @@ -33,9 +50,11 @@ namespace Content.Shared.VendingMachines [Serializable, NetSerializable] public sealed class VendingMachineEjectMessage : BoundUserInterfaceMessage { + public readonly InventoryType Type; public readonly string ID; - public VendingMachineEjectMessage(string id) + public VendingMachineEjectMessage(InventoryType type, string id) { + Type = type; ID = id; } } @@ -64,12 +83,14 @@ namespace Content.Shared.VendingMachines [Serializable, NetSerializable] public sealed class VendingMachineInventoryEntry { + [ViewVariables(VVAccess.ReadWrite)] public InventoryType Type; [ViewVariables(VVAccess.ReadWrite)] public string ID; [ViewVariables(VVAccess.ReadWrite)] public uint Amount; - public VendingMachineInventoryEntry(string id, uint amount) + public VendingMachineInventoryEntry(InventoryType type, string id, uint amount) { + Type = type; ID = id; Amount = amount; } @@ -82,5 +103,26 @@ namespace Content.Shared.VendingMachines Advertisement, Limiter } + + [Serializable, NetSerializable] + public enum InventoryType : byte + { + Regular, + Emagged, + Contraband + } + } + + [Serializable, NetSerializable] + public enum ContrabandWireKey : byte + { + StatusKey, + TimeoutKey + } + + [Serializable, NetSerializable] + public enum EjectWireKey : byte + { + StatusKey, } } diff --git a/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs b/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs index 84e57dc9b6..d38715fd8f 100644 --- a/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs +++ b/Content.Shared/VendingMachines/VendingMachineInventoryPrototype.cs @@ -25,5 +25,11 @@ namespace Content.Shared.VendingMachines [DataField("startingInventory")] public Dictionary StartingInventory { get; } = new(); + + [DataField("emaggedInventory")] + public Dictionary? EmaggedInventory { get; } + + [DataField("contrabandInventory")] + public Dictionary? ContrabandInventory { get; } } } diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/chapel.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/chapel.yml index e4ca117a07..99902ec506 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/chapel.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/chapel.yml @@ -16,11 +16,7 @@ ClothingOuterPlagueSuit: 1 ClothingMaskPlague: 1 ClothingHeadsetService: 2 - -- type: vendingMachineInventory - id: PietyVendEmagInventory - spriteName: chapel - startingInventory: + emaggedInventory: ClothingOuterArmorCult: 1 ClothingHeadHelmetCult: 1 ClothingOuterRobesCult: 3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml index 73ce44a8ac..004b017fac 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml @@ -7,10 +7,12 @@ Handcuffs: 8 GrenadeFlashBang: 4 Flash: 5 - FoodDonutHomer: 12 - FoodBoxDonut: 2 FlashlightLantern: 4 #seclite ClothingEyesGlassesSunglasses: 2 ClothingBeltSecurityWebbing: 5 Zipties: 12 + # security officers need to follow a diet regimen! + contrabandInventory: + FoodDonutHomer: 12 + FoodBoxDonut: 2 #box evidence diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 2ce09b2b7a..88844901a0 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -932,7 +932,6 @@ components: - type: VendingMachine pack: PietyVendInventory - emagPack: PietyVendEmagInventory - type: Sprite sprite: Structures/Machines/VendingMachines/chapdrobe.rsi layers: diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 61413a2e6e..5ed7dc1a39 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -23,10 +23,11 @@ - type: wireLayout id: Vending - dummyWires: 2 wires: - !type:PowerWireAction - !type:AccessWireAction + - !type:VendingMachineContrabandWireAction + - !type:VendingMachineEjectItemWireAction - type: wireLayout id: AirAlarm