diff --git a/Content.Server/Destructible/Thresholds/Behaviors/EjectVendorItems.cs b/Content.Server/Destructible/Thresholds/Behaviors/EjectVendorItems.cs new file mode 100644 index 0000000000..c0d267f0d2 --- /dev/null +++ b/Content.Server/Destructible/Thresholds/Behaviors/EjectVendorItems.cs @@ -0,0 +1,47 @@ +using Content.Server.VendingMachines; +using Content.Shared.Throwing; +using Robust.Shared.Random; + +namespace Content.Server.Destructible.Thresholds.Behaviors +{ + /// + /// Throws out a specific amount of random items from a vendor + /// + [Serializable] + [DataDefinition] + public sealed class EjectVendorItems : IThresholdBehavior + { + /// + /// The percent amount of the total inventory that will be ejected. + /// + [DataField("percent", required: true)] + public float Percent = 0.25f; + + /// + /// The maximum amount of vendor items it can eject + /// useful for high-inventory vendors + /// + [DataField("max")] + public int Max = 3; + + public void Execute(EntityUid owner, DestructibleSystem system) + { + if (!system.EntityManager.TryGetComponent(owner, out var vendingcomp) || + !system.EntityManager.TryGetComponent(owner, out var xform)) + return; + + var throwingsys = system.EntityManager.EntitySysManager.GetEntitySystem(); + var totalItems = vendingcomp.AllInventory.Count; + + var toEject = Math.Min(totalItems * Percent, Max); + for (var i = 0; i < toEject; i++) + { + var entity = system.EntityManager.SpawnEntity(system.Random.PickAndTake(vendingcomp.AllInventory).ID, xform.Coordinates); + + float range = vendingcomp.NonLimitedEjectRange; + Vector2 direction = new Vector2(system.Random.NextFloat(-range, range), system.Random.NextFloat(-range, range)); + throwingsys.TryThrow(entity, direction, vendingcomp.NonLimitedEjectForce); + } + } + } +} diff --git a/Content.Server/VendingMachines/VendingMachineComponent.cs b/Content.Server/VendingMachines/VendingMachineComponent.cs index 244bc7fd4b..2db45ad428 100644 --- a/Content.Server/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/VendingMachines/VendingMachineComponent.cs @@ -1,4 +1,5 @@ using Content.Server.UserInterface; +using Content.Shared.Actions.ActionTypes; using Content.Shared.Sound; using Content.Shared.VendingMachines; using Robust.Server.GameObjects; @@ -26,6 +27,16 @@ namespace Content.Server.VendingMachines [DataField("speedLimiter")] public bool CanShoot = false; + /// + /// The chance that a vending machine will randomly dispense an item on hit. + /// Chance is 0 if null. + /// + [DataField("dispenseOnHitChance")] + public float? DispenseOnHitChance; + + [DataField("dispenseOnHitThreshold")] + public float? DispenseOnHitThreshold; + [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"); @@ -34,6 +45,9 @@ namespace Content.Server.VendingMachines // 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"); + [DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? Action = "VendingThrow"; + [ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key); public float NonLimitedEjectForce = 7.5f; diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index a5d873182b..90b1e51f41 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -4,6 +4,9 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Damage; using Content.Shared.Destructible; using Content.Shared.Emag.Systems; using Content.Shared.Throwing; @@ -24,6 +27,7 @@ namespace Content.Server.VendingMachines [Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + [Dependency] private readonly SharedActionsSystem _action = default!; public override void Initialize() { @@ -34,6 +38,9 @@ namespace Content.Server.VendingMachines SubscribeLocalEvent(OnInventoryEjectMessage); SubscribeLocalEvent(OnBreak); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnDamage); + + SubscribeLocalEvent(OnSelfDispense); } private void OnComponentInit(EntityUid uid, VendingMachineComponent component, ComponentInit args) @@ -45,6 +52,12 @@ namespace Content.Server.VendingMachines TryUpdateVisualState(uid, null, component); } + if (component.Action != null) + { + var action = new InstantAction(_prototypeManager.Index(component.Action)); + _action.AddAction(uid, action, uid); + } + InitializeFromPrototype(uid, component); } @@ -91,6 +104,24 @@ namespace Content.Server.VendingMachines component.Emagged = true; args.Handled = true; } + + private void OnDamage(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args) + { + if (component.DispenseOnHitChance == null || args.DamageDelta == null) + return; + + if (args.DamageDelta.Total >= component.DispenseOnHitThreshold && _random.Prob(component.DispenseOnHitChance.Value)) + EjectRandom(uid, true, component); + } + + private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + EjectRandom(uid, true, component); + } public void InitializeFromPrototype(EntityUid uid, VendingMachineComponent? vendComponent = null) { diff --git a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs index a122c6d4de..6084ec0f76 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Actions; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -121,4 +122,6 @@ namespace Content.Shared.VendingMachines { StatusKey, } + + public sealed class VendingMachineSelfDispenseEvent : InstantActionEvent { }; } diff --git a/Resources/Locale/en-US/vending-machines/vending-machine-component.ftl b/Resources/Locale/en-US/vending-machines/vending-machine-component.ftl index 3cd6b38d66..0991982241 100644 --- a/Resources/Locale/en-US/vending-machines/vending-machine-component.ftl +++ b/Resources/Locale/en-US/vending-machines/vending-machine-component.ftl @@ -2,4 +2,9 @@ vending-machine-component-try-eject-invalid-item = Invalid item vending-machine-component-try-eject-out-of-stock = Out of stock -vending-machine-component-try-eject-access-denied = Access denied \ No newline at end of file +vending-machine-component-try-eject-access-denied = Access denied + +## VendingMachineSelfDespense Action + +vending-machine-action-name = Dispense Item +vending-machine-action-description = Randomly dispense an item from your stock. \ No newline at end of file diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 0269fb0952..8c3d9ea9f6 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -48,3 +48,10 @@ userPopup: action-popup-combat popupToggleSuffix: -disabling event: !type:ToggleCombatActionEvent + +- type: instantAction + id: VendingThrow + name: vending-machine-action-name + description: vending-machine-action-description + useDelay: 30 + event: !type:VendingMachineSelfDispenseEvent diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index d5460219d5..46b6f95ffa 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -37,6 +37,7 @@ behaviors: - !type:DoActsBehavior acts: ["Breakage"] + - !type:EjectVendorItems - type: ActivatableUI key: enum.VendingMachineUiKey.Key - type: ActivatableUIRequiresPower @@ -64,6 +65,7 @@ - type: ApcPowerReceiver powerLoad: 200 priority: Low + - type: Actions - type: SentienceTarget flavorKind: mechanical @@ -166,6 +168,8 @@ components: - type: VendingMachine pack: CigaretteMachineInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: CigaretteMachineAds - type: Speech @@ -232,6 +236,8 @@ components: - type: VendingMachine pack: HotDrinksMachineInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: HotDrinksMachineAds - type: Speech @@ -270,6 +276,8 @@ components: - type: VendingMachine pack: RobustSoftdrinksInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: RobustSoftdrinksAds - type: Speech @@ -338,6 +346,8 @@ components: - type: VendingMachine pack: DiscountDansInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: DiscountDansAds - type: Speech @@ -574,6 +584,8 @@ components: - type: VendingMachine pack: GetmoreChocolateCorpInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: GetmoreChocolateCorpAds - type: Speech @@ -608,6 +620,8 @@ components: - type: VendingMachine pack: BodaInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: BodaAds - type: Speech @@ -815,6 +829,8 @@ components: - type: VendingMachine pack: ChangInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 - type: Advertise pack: ChangAds - type: Speech