diff --git a/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs new file mode 100644 index 0000000000..49207b5f55 --- /dev/null +++ b/Content.IntegrationTests/Tests/VendingMachineRestockTest.cs @@ -0,0 +1,360 @@ +#nullable enable +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Content.Server.Storage.Components; +using Content.Server.VendingMachines; +using Content.Server.VendingMachines.Restock; +using Content.Server.Wires; +using Content.Shared.Cargo.Prototypes; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.VendingMachines; + +namespace Content.IntegrationTests.Tests +{ + [TestFixture] + [TestOf(typeof(VendingMachineRestockComponent))] + [TestOf(typeof(VendingMachineRestockSystem))] + public sealed class VendingMachineRestockTest : EntitySystem + { + private const string Prototypes = @" +- type: entity + name: HumanDummy + id: HumanDummy + components: + - type: Hands + - type: Body + prototype: Human + +- type: entity + parent: FoodSnackBase + id: TestRamen + name: TestRamen + +- type: vendingMachineInventory + id: TestInventory + startingInventory: + TestRamen: 1 + +- type: vendingMachineInventory + id: OtherTestInventory + startingInventory: + TestRamen: 3 + +- type: vendingMachineInventory + id: BigTestInventory + startingInventory: + TestRamen: 4 + +- type: entity + parent: BaseVendingMachineRestock + id: TestRestockWrong + name: TestRestockWrong + components: + - type: VendingMachineRestock + canRestock: + - OtherTestInventory + +- type: entity + parent: BaseVendingMachineRestock + id: TestRestockCorrect + name: TestRestockCorrect + components: + - type: VendingMachineRestock + canRestock: + - TestInventory + +- type: entity + parent: BaseVendingMachineRestock + id: TestRestockExplode + name: TestRestockExplode + components: + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 20 + behaviors: + - !type:DumpRestockInventory + - !type:DoActsBehavior + acts: [ 'Destruction' ] + - type: VendingMachineRestock + canRestock: + - BigTestInventory + +- type: entity + parent: VendingMachine + id: VendingMachineTest + name: Test Ramen + components: + - type: Wires + LayoutId: Vending + - type: VendingMachine + pack: TestInventory + - type: Sprite + sprite: error.rsi +"; + + [Test] + public async Task TestAllRestocksAreAvailableToBuy() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); + var server = pairTracker.Pair.Server; + await server.WaitIdleAsync(); + + var prototypeManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + HashSet restocks = new(); + Dictionary> restockStores = new(); + + // Collect all the prototypes with restock components. + foreach (var proto in prototypeManager.EnumeratePrototypes()) + { + if (proto.Abstract || + !proto.TryGetComponent(out var restock)) + { + continue; + } + + restocks.Add(proto.ID); + } + + // Collect all the prototypes with StorageFills referencing those entities. + foreach (var proto in prototypeManager.EnumeratePrototypes()) + { + if (!proto.TryGetComponent(out var storage)) + continue; + + List restockStore = new(); + foreach (var spawnEntry in storage.Contents) + { + if (spawnEntry.PrototypeId != null && restocks.Contains(spawnEntry.PrototypeId)) + restockStore.Add(spawnEntry.PrototypeId); + } + + if (restockStore.Count > 0) + restockStores.Add(proto.ID, restockStore); + } + + // Iterate through every CargoProduct and make sure each + // prototype with a restock component is referenced in a + // purchaseable entity with a StorageFill. + foreach (var proto in prototypeManager.EnumeratePrototypes()) + { + if (restockStores.ContainsKey(proto.Product)) + { + foreach (var entry in restockStores[proto.Product]) + restocks.Remove(entry); + + restockStores.Remove(proto.Product); + } + } + + Assert.That(restockStores.Count, Is.EqualTo(0), + $"Some entities containing entities with VendingMachineRestock components are unavailable for purchase: \n - {string.Join("\n - ", restockStores.Keys)}"); + + Assert.That(restocks.Count, Is.EqualTo(0), + $"Some entities with VendingMachineRestock components are unavailable for purchase: \n - {string.Join("\n - ", restocks)}"); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestCompleteRestockProcess() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + var server = pairTracker.Pair.Server; + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var entitySystemManager = server.ResolveDependency(); + + EntityUid packageRight; + EntityUid packageWrong; + EntityUid machine; + EntityUid user; + VendingMachineComponent machineComponent; + VendingMachineRestockComponent restockRightComponent; + VendingMachineRestockComponent restockWrongComponent; + WiresComponent machineWires; + + var testMap = await PoolManager.CreateTestMap(pairTracker); + + await server.WaitAssertion(() => + { + var coordinates = testMap.GridCoords; + + // Spawn the entities. + user = entityManager.SpawnEntity("HumanDummy", coordinates); + machine = entityManager.SpawnEntity("VendingMachineTest", coordinates); + packageRight = entityManager.SpawnEntity("TestRestockCorrect", coordinates); + packageWrong = entityManager.SpawnEntity("TestRestockWrong", coordinates); + + // Sanity test for components existing. + Assert.True(entityManager.TryGetComponent(machine, out machineComponent!), $"Machine has no {nameof(VendingMachineComponent)}"); + Assert.True(entityManager.TryGetComponent(packageRight, out restockRightComponent!), $"Correct package has no {nameof(VendingMachineRestockComponent)}"); + Assert.True(entityManager.TryGetComponent(packageWrong, out restockWrongComponent!), $"Wrong package has no {nameof(VendingMachineRestockComponent)}"); + Assert.True(entityManager.TryGetComponent(machine, out machineWires!), $"Machine has no {nameof(WiresComponent)}"); + + var systemRestock = entitySystemManager.GetEntitySystem(); + var systemMachine = entitySystemManager.GetEntitySystem(); + + // Test that the panel needs to be opened first. + Assert.That(systemRestock.TryAccessMachine(packageRight, restockRightComponent, machineComponent, user, machine), Is.False, "Right package is able to restock without opened access panel"); + Assert.That(systemRestock.TryAccessMachine(packageWrong, restockWrongComponent, machineComponent, user, machine), Is.False, "Wrong package is able to restock without opened access panel"); + + // Open the panel. + machineWires.IsPanelOpen = true; + + // Test that the right package works for the right machine. + Assert.That(systemRestock.TryAccessMachine(packageRight, restockRightComponent, machineComponent, user, machine), Is.True, "Correct package is unable to restock with access panel opened"); + + // Test that the wrong package does not work. + Assert.That(systemRestock.TryMatchPackageToMachine(packageWrong, restockWrongComponent, machineComponent, user, machine), Is.False, "Package with invalid canRestock is able to restock machine"); + + // Test that the right package does work. + Assert.That(systemRestock.TryMatchPackageToMachine(packageRight, restockRightComponent, machineComponent, user, machine), Is.True, "Package with valid canRestock is unable to restock machine"); + + // Make sure there's something in there to begin with. + Assert.That(systemMachine.GetAvailableInventory(machine, machineComponent).Count, Is.GreaterThan(0), + "Machine inventory is empty before emptying."); + + // Empty the inventory. + systemMachine.EjectRandom(machine, false, true, machineComponent); + Assert.That(systemMachine.GetAvailableInventory(machine, machineComponent).Count, Is.EqualTo(0), + "Machine inventory is not empty after ejecting."); + + // Test that the inventory is actually restocked. + systemMachine.TryRestockInventory(machine, machineComponent); + Assert.That(systemMachine.GetAvailableInventory(machine, machineComponent).Count, Is.GreaterThan(0), + "Machine available inventory count is not greater than zero after restock."); + + mapManager.DeleteMap(testMap.MapId); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestRestockBreaksOpen() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + var server = pairTracker.Pair.Server; + await server.WaitIdleAsync(); + + var prototypeManager = server.ResolveDependency(); + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var entitySystemManager = server.ResolveDependency(); + + var damageableSystem = entitySystemManager.GetEntitySystem(); + + var testMap = await PoolManager.CreateTestMap(pairTracker); + + EntityUid restock = default; + + await server.WaitAssertion(() => + { + var coordinates = testMap.GridCoords; + + var totalStartingRamen = 0; + + foreach (var meta in entityManager.EntityQuery()) + if (!meta.Deleted && meta.EntityPrototype?.ID == "TestRamen") + totalStartingRamen++; + + Assert.That(totalStartingRamen, Is.EqualTo(0), + "Did not start with zero ramen."); + + restock = entityManager.SpawnEntity("TestRestockExplode", coordinates); + var damageSpec = new DamageSpecifier(prototypeManager.Index("Blunt"), 100); + var damageResult = damageableSystem.TryChangeDamage(restock, damageSpec); + + Assert.IsNotNull(damageResult, + "Received null damageResult when attempting to damage restock box."); + + Assert.That((int) damageResult!.Total, Is.GreaterThan(0), + "Box damage result was not greater than 0."); + }); + await server.WaitRunTicks(15); + await server.WaitAssertion(() => + { + Assert.That(entityManager.Deleted(restock), + "Restock box was not deleted after being damaged."); + + var totalRamen = 0; + + foreach (var meta in entityManager.EntityQuery()) + if (!meta.Deleted && meta.EntityPrototype?.ID == "TestRamen") + totalRamen++; + + Assert.That(totalRamen, Is.EqualTo(2), + "Did not find enough ramen after destroying restock box."); + + mapManager.DeleteMap(testMap.MapId); + }); + + await pairTracker.CleanReturnAsync(); + } + + [Test] + public async Task TestRestockInventoryBounds() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); + var server = pairTracker.Pair.Server; + await server.WaitIdleAsync(); + + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + var entitySystemManager = server.ResolveDependency(); + + var vendingMachineSystem = entitySystemManager.GetEntitySystem(); + + var testMap = await PoolManager.CreateTestMap(pairTracker); + + await server.WaitAssertion(() => + { + var coordinates = testMap.GridCoords; + + EntityUid machine = entityManager.SpawnEntity("VendingMachineTest", coordinates); + + Assert.That(vendingMachineSystem.GetAvailableInventory(machine).Count, Is.EqualTo(1), + "Machine's available inventory did not contain one entry."); + + Assert.That(vendingMachineSystem.GetAvailableInventory(machine)[0].Amount, Is.EqualTo(1), + "Machine's available inventory is not the expected amount."); + + vendingMachineSystem.RestockInventoryFromPrototype(machine); + + Assert.That(vendingMachineSystem.GetAvailableInventory(machine)[0].Amount, Is.EqualTo(2), + "Machine's available inventory is not double its starting amount after a restock."); + + vendingMachineSystem.RestockInventoryFromPrototype(machine); + + Assert.That(vendingMachineSystem.GetAvailableInventory(machine)[0].Amount, Is.EqualTo(3), + "Machine's available inventory is not triple its starting amount after two restocks."); + + vendingMachineSystem.RestockInventoryFromPrototype(machine); + + Assert.That(vendingMachineSystem.GetAvailableInventory(machine)[0].Amount, Is.EqualTo(3), + "Machine's available inventory did not stay the same after a third restock."); + }); + + await pairTracker.CleanReturnAsync(); + } + } +} + +#nullable disable diff --git a/Content.Server/Destructible/Thresholds/Behaviors/DumpRestockInventory.cs b/Content.Server/Destructible/Thresholds/Behaviors/DumpRestockInventory.cs new file mode 100644 index 0000000000..80553f2d33 --- /dev/null +++ b/Content.Server/Destructible/Thresholds/Behaviors/DumpRestockInventory.cs @@ -0,0 +1,61 @@ +using Robust.Shared.Random; +using Content.Shared.Stacks; +using Content.Server.VendingMachines.Restock; +using Content.Shared.Prototypes; +using Content.Shared.VendingMachines; + +namespace Content.Server.Destructible.Thresholds.Behaviors +{ + /// + /// Spawns a portion of the total items from one of the canRestock + /// inventory entries on a VendingMachineRestock component. + /// + [Serializable] + [DataDefinition] + public sealed class DumpRestockInventory: IThresholdBehavior + { + /// + /// The percent of each inventory entry that will be salvaged + /// upon destruction of the package. + /// + [DataField("percent", required: true)] + public float Percent = 0.5f; + + [DataField("offset")] + public float Offset { get; set; } = 0.5f; + + public void Execute(EntityUid owner, DestructibleSystem system) + { + if (!system.EntityManager.TryGetComponent(owner, out var packagecomp) || + !system.EntityManager.TryGetComponent(owner, out var xform)) + return; + + var randomInventory = system.Random.Pick(packagecomp.CanRestock); + + if (!system.PrototypeManager.TryIndex(randomInventory, out VendingMachineInventoryPrototype? packPrototype)) + return; + + foreach (var (entityId, count) in packPrototype.StartingInventory) + { + var toSpawn = (int) Math.Round(count * Percent); + + if (toSpawn == 0) continue; + + if (EntityPrototypeHelpers.HasComponent(entityId, system.PrototypeManager, system.ComponentFactory)) + { + var spawned = system.EntityManager.SpawnEntity(entityId, xform.Coordinates.Offset(system.Random.NextVector2(-Offset, Offset))); + system.StackSystem.SetCount(spawned, toSpawn); + system.EntityManager.GetComponent(spawned).LocalRotation = system.Random.NextAngle(); + } + else + { + for (var i = 0; i < toSpawn; i++) + { + var spawned = system.EntityManager.SpawnEntity(entityId, xform.Coordinates.Offset(system.Random.NextVector2(-Offset, Offset))); + system.EntityManager.GetComponent(spawned).LocalRotation = system.Random.NextAngle(); + } + } + } + } + } +} diff --git a/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs b/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs new file mode 100644 index 0000000000..1825c407cd --- /dev/null +++ b/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs @@ -0,0 +1,42 @@ +using System.Threading; +using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Content.Shared.VendingMachines; + +namespace Content.Server.VendingMachines.Restock +{ + [RegisterComponent] + public sealed class VendingMachineRestockComponent : Component + { + public CancellationTokenSource? CancelToken; + + /// + /// The time (in seconds) that it takes to restock a machine. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("restockDelay")] + public TimeSpan RestockDelay = TimeSpan.FromSeconds(8.0f); + + /// + /// What sort of machine inventory does this restock? + /// This is checked against the VendingMachineComponent's pack value. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("canRestock", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet CanRestock = new(); + + /// + /// Sound that plays when starting to restock a machine. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("soundRestockStart")] + public SoundSpecifier SoundRestockStart = new SoundPathSpecifier("/Audio/Machines/vending_restock_start.ogg"); + + /// + /// Sound that plays when finished restocking a machine. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("soundRestockDone")] + public SoundSpecifier SoundRestockDone = new SoundPathSpecifier("/Audio/Machines/vending_restock_done.ogg"); + } +} diff --git a/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs new file mode 100644 index 0000000000..b1a95f0727 --- /dev/null +++ b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs @@ -0,0 +1,148 @@ +using System.Linq; +using System.Threading; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Content.Server.Cargo.Systems; +using Content.Server.DoAfter; +using Content.Server.Wires; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.VendingMachines; + +namespace Content.Server.VendingMachines.Restock +{ + public sealed class VendingMachineRestockSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly PricingSystem _pricingSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnPriceCalculation); + SubscribeLocalEvent(OnRestockCancelled); + } + + public bool TryAccessMachine(EntityUid uid, + VendingMachineRestockComponent component, + VendingMachineComponent machineComponent, + EntityUid user, + EntityUid target) + { + if (!TryComp(target, out var wires) || !wires.IsPanelOpen) { + _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-needs-panel-open", + ("this", uid), + ("user", user), + ("target", target)), + user); + return false; + } + + return true; + } + + public bool TryMatchPackageToMachine(EntityUid uid, + VendingMachineRestockComponent component, + VendingMachineComponent machineComponent, + EntityUid user, + EntityUid target) + { + if (!component.CanRestock.Contains(machineComponent.PackPrototypeId)) { + _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-invalid-inventory", + ("this", uid), + ("user", user), + ("target", target) + ), + user); + return false; + } + + return true; + } + + private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args) + { + if (component.CancelToken != null || args.Target == null || !args.CanReach) + return; + + if (!TryComp(args.Target, out var machineComponent)) + return; + + if (!TryMatchPackageToMachine(uid, component, machineComponent, args.User, args.Target.Value)) + return; + + if (!TryAccessMachine(uid, component, machineComponent, args.User, args.Target.Value)) + return; + + component.CancelToken = new CancellationTokenSource(); + + _doAfterSystem.DoAfter(new DoAfterEventArgs( + args.User, + (float) component.RestockDelay.TotalSeconds, + component.CancelToken.Token, + args.Target, + args.Used) + { + TargetFinishedEvent = new VendingMachineRestockEvent(args.User, uid), + UsedCancelledEvent = new RestockCancelledEvent(), + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnStun = true, + BreakOnDamage = true, + NeedHand = true + }); + + _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", + ("this", uid), + ("user", args.User), + ("target", args.Target) + ), + args.User, + PopupType.Medium); + + _audioSystem.PlayPvs(component.SoundRestockStart, component.Owner, + AudioParams.Default + .WithVolume(-2f) + .WithVariation(0.2f)); + } + + 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 (_prototypeManager.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype)) + { + foreach (var (item, amount) in inventoryPrototype.StartingInventory) + { + if (_prototypeManager.TryIndex(item, out EntityPrototype? entity)) + total += _pricingSystem.GetEstimatedPrice(entity) * amount; + } + } + + priceSets.Add(total); + } + + args.Price += priceSets.Max(); + } + + private void OnRestockCancelled(EntityUid uid, VendingMachineRestockComponent component, RestockCancelledEvent args) + { + component.CancelToken?.Cancel(); + component.CancelToken = null; + } + + public readonly struct RestockCancelledEvent { } + } +} diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 85e416f806..a46f955385 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.UserInterface; +using Content.Server.VendingMachines.Restock; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Actions; @@ -10,6 +11,7 @@ using Content.Shared.Actions.ActionTypes; using Content.Shared.Damage; using Content.Shared.Destructible; using Content.Shared.Emag.Systems; +using Content.Shared.Popups; using Content.Shared.Throwing; using Content.Shared.VendingMachines; using Robust.Server.GameObjects; @@ -51,6 +53,8 @@ namespace Content.Server.VendingMachines SubscribeLocalEvent(OnInventoryEjectMessage); SubscribeLocalEvent(OnSelfDispense); + + SubscribeLocalEvent(OnRestock); } private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args) @@ -160,6 +164,31 @@ namespace Content.Server.VendingMachines EjectRandom(uid, throwItem: true, forceEject: false, component); } + private void OnRestock(EntityUid uid, VendingMachineComponent component, VendingMachineRestockEvent args) + { + if (!TryComp(args.RestockBox, out var restockComponent)) + { + _sawmill.Error($"{ToPrettyString(args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.RestockBox)} which did not have a VendingMachineRestockComponent."); + return; + } + + TryRestockInventory(uid, component); + + _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-done", + ("this", args.RestockBox), + ("user", args.User), + ("target", uid)), + args.User, + PopupType.Medium); + + _audioSystem.PlayPvs(restockComponent.SoundRestockDone, component.Owner, + AudioParams.Default + .WithVolume(-2f) + .WithVariation(0.2f)); + + Del(args.RestockBox); + } + /// /// Sets the property of the vending machine. /// @@ -412,5 +441,29 @@ namespace Content.Server.VendingMachines } } } + + public void TryRestockInventory(EntityUid uid, VendingMachineComponent? vendComponent = null) + { + if (!Resolve(uid, ref vendComponent)) + return; + + RestockInventoryFromPrototype(uid, vendComponent); + + UpdateVendingMachineInterfaceState(vendComponent); + TryUpdateVisualState(uid, vendComponent); + } + + } + + public sealed class VendingMachineRestockEvent : EntityEventArgs + { + public EntityUid User { get; } + public EntityUid RestockBox { get; } + + public VendingMachineRestockEvent(EntityUid user, EntityUid restockBox) + { + User = user; + RestockBox = restockBox; + } } } diff --git a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs index 11436fa57c..96b1f0a1d1 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs @@ -15,6 +15,17 @@ public abstract class SharedVendingMachineSystem : EntitySystem protected virtual void OnComponentInit(EntityUid uid, VendingMachineComponent component, ComponentInit args) { + RestockInventoryFromPrototype(uid, component); + } + + public void RestockInventoryFromPrototype(EntityUid uid, + VendingMachineComponent? component = null) + { + if (!Resolve(uid, ref component)) + { + return; + } + if (!_prototypeManager.TryIndex(component.PackPrototypeId, out VendingMachineInventoryPrototype? packPrototype)) return; @@ -64,28 +75,38 @@ public abstract class SharedVendingMachineSystem : EntitySystem return; } - var inventory = new Dictionary(); + Dictionary inventory; + switch (type) + { + case InventoryType.Regular: + inventory = component.Inventory; + break; + case InventoryType.Emagged: + inventory = component.EmaggedInventory; + break; + case InventoryType.Contraband: + inventory = component.ContrabandInventory; + break; + default: + return; + } foreach (var (id, amount) in entries) { if (_prototypeManager.HasIndex(id)) { - inventory.Add(id, new VendingMachineInventoryEntry(type, id, amount)); + if (inventory.TryGetValue(id, out VendingMachineInventoryEntry? entry)) + // Prevent a machine's stock from going over three times + // the prototype's normal amount. This is an arbitrary + // number and meant to be a convenience for someone + // restocking a machine who doesn't want to force vend out + // all the items just to restock one empty slot without + // losing the rest of the restock. + entry.Amount = Math.Min(entry.Amount + amount, 3 * amount); + else + inventory.Add(id, new VendingMachineInventoryEntry(type, id, amount)); } } - - switch (type) - { - case InventoryType.Regular: - component.Inventory = inventory; - break; - case InventoryType.Emagged: - component.EmaggedInventory = inventory; - break; - case InventoryType.Contraband: - component.ContrabandInventory = inventory; - break; - } } } diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index fa297cd1d6..64bb2793db 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -1,3 +1,13 @@ +- files: ["vending_restock_start.ogg"] + license: "CC0-1.0" + copyright: "https://freesound.org/people/Defaultv/" + source: "https://freesound.org/people/Defaultv/sounds/534362/" + +- files: ["vending_restock_done.ogg"] + license: "CC-BY-3.0" + copyright: "https://freesound.org/people/felipelnv/" + source: "https://freesound.org/people/felipelnv/sounds/153298/" + - files: ["short_print_and_rip.ogg"] license: "CC0-1.0" copyright: "receipt printing.wav by 13F_Panska_Tlolkova_Matilda. This version is cleaned up, shortened, and converted to OGG." diff --git a/Resources/Audio/Machines/vending_restock_done.ogg b/Resources/Audio/Machines/vending_restock_done.ogg new file mode 100644 index 0000000000..e1386f4df1 Binary files /dev/null and b/Resources/Audio/Machines/vending_restock_done.ogg differ diff --git a/Resources/Audio/Machines/vending_restock_start.ogg b/Resources/Audio/Machines/vending_restock_start.ogg new file mode 100644 index 0000000000..61e931a999 Binary files /dev/null and b/Resources/Audio/Machines/vending_restock_start.ogg differ diff --git a/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-vending.ftl b/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-vending.ftl new file mode 100644 index 0000000000..53a3fd77bc --- /dev/null +++ b/Resources/Locale/en-US/prototypes/catalog/cargo/cargo-vending.ftl @@ -0,0 +1,48 @@ +ent-CrateVendingMachineRestockBooze = { ent-CrateVendingMachineRestockBoozeFilled } + .desc = { ent-CrateVendingMachineRestockBoozeFilled.desc } + +ent-CrateVendingMachineRestockClothes = { ent-CrateVendingMachineRestockClothesFilled } + .desc = { ent-CrateVendingMachineRestockClothesFilled.desc } + +ent-CrateVendingMachineRestockDinnerware = { ent-CrateVendingMachineRestockDinnerwareFilled } + .desc = { ent-CrateVendingMachineRestockDinnerwareFilled.desc } + +ent-CrateVendingMachineRestockEngineering = { ent-CrateVendingMachineRestockEngineeringFilled } + .desc = { ent-CrateVendingMachineRestockEngineeringFilled.desc } + +ent-CrateVendingMachineRestockGames = { ent-CrateVendingMachineRestockGamesFilled } + .desc = { ent-CrateVendingMachineRestockGamesFilled.desc } + +ent-CrateVendingMachineRestockHotDrinks = { ent-CrateVendingMachineRestockHotDrinksFilled } + .desc = { ent-CrateVendingMachineRestockHotDrinksFilled.desc } + +ent-CrateVendingMachineRestockMedical = { ent-CrateVendingMachineRestockMedicalFilled } + .desc = { ent-CrateVendingMachineRestockMedicalFilled.desc } + +ent-CrateVendingMachineRestockNutriMax = { ent-CrateVendingMachineRestockNutriMaxFilled } + .desc = { ent-CrateVendingMachineRestockNutriMaxFilled.desc } + +ent-CrateVendingMachineRestockPTech = { ent-CrateVendingMachineRestockPTechFilled } + .desc = { ent-CrateVendingMachineRestockPTechFilled.desc } + +ent-CrateVendingMachineRestockRobustSoftdrinks = { ent-CrateVendingMachineRestockRobustSoftdrinksFilled } + .desc = { ent-CrateVendingMachineRestockRobustSoftdrinksFilled.desc } + +ent-CrateVendingMachineRestockSalvageEquipment = { ent-CrateVendingMachineRestockSalvageEquipmentFilled } + .desc = { ent-CrateVendingMachineRestockSalvageEquipmentFilled.desc } + +ent-CrateVendingMachineRestockSecTech = { ent-CrateVendingMachineRestockSecTechFilled } + .desc = { ent-CrateVendingMachineRestockSecTechFilled.desc } + +ent-CrateVendingMachineRestockSeeds = { ent-CrateVendingMachineRestockSeedsFilled } + .desc = { ent-CrateVendingMachineRestockSeedsFilled.desc } + +ent-CrateVendingMachineRestockSmokes = { ent-CrateVendingMachineRestockSmokesFilled } + .desc = { ent-CrateVendingMachineRestockSmokesFilled.desc } + +ent-CrateVendingMachineRestockSnacks = { ent-CrateVendingMachineRestockSnacksFilled } + .desc = { ent-CrateVendingMachineRestockSnacksFilled.desc } + +ent-CrateVendingMachineRestockTankDispenser = { ent-CrateVendingMachineRestockTankDispenserFilled } + .desc = { ent-CrateVendingMachineRestockTankDispenserFilled.desc } + diff --git a/Resources/Locale/en-US/prototypes/catalog/fills/crates/vending-crates.ftl b/Resources/Locale/en-US/prototypes/catalog/fills/crates/vending-crates.ftl new file mode 100644 index 0000000000..4b8451d19e --- /dev/null +++ b/Resources/Locale/en-US/prototypes/catalog/fills/crates/vending-crates.ftl @@ -0,0 +1,47 @@ +ent-CrateVendingMachineRestockBoozeFilled = Booze-O-Mat restock crate + .desc = Contains a restock box for the Booze-O-Mat. + +ent-CrateVendingMachineRestockClothesFilled = Clothing restock crate + .desc = Contains a pair of restock boxes, one for the ClothesMate and one for the AutoDrobe. + +ent-CrateVendingMachineRestockDinnerwareFilled = Plasteel Chef restock crate + .desc = Contains a restock box for the Plasteel Chef vending machine. + +ent-CrateVendingMachineRestockEngineeringFilled = EngiVend restock crate + .desc = Contains a restock box for the EngiVend. Also supports the YouTool. + +ent-CrateVendingMachineRestockGamesFilled = Good Clean Fun restock crate + .desc = Contains a restock box for the Good Clean Fun vending machine. + +ent-CrateVendingMachineRestockHotDrinksFilled = Solar's Best restock crate + .desc = Contains two restock boxes for Solar's Best Hot Drinks vending machine. + +ent-CrateVendingMachineRestockMedicalFilled = NanoMed restock crate + .desc = Contains a restock box, compatible with the NanoMed and NanoMedPlus. + +ent-CrateVendingMachineRestockNutriMaxFilled = NutriMax restock crate + .desc = Contains a restock box for the NutriMax vending machine. + +ent-CrateVendingMachineRestockPTechFilled = PTech restock crate + .desc = Contains a restock box for the PTech bureaucracy dispenser. + +ent-CrateVendingMachineRestockRobustSoftdrinksFilled = Robust Softdrinks restock crate + .desc = Contains two restock boxes for the Robust Softdrinks LLC vending machine. + +ent-CrateVendingMachineRestockSalvageEquipmentFilled = Salvage restock crate + .desc = Contains a restock box for the salvage vendor. + +ent-CrateVendingMachineRestockSecTechFilled = SecTech restock crate + .desc = Contains a restock box for the SecTech vending machine. + +ent-CrateVendingMachineRestockSeedsFilled = MegaSeed restock crate + .desc = Contains a restock box for the MegaSeed vending machine. + +ent-CrateVendingMachineRestockSmokesFilled = ShadyCigs restock crate + .desc = Contains two restock boxes for the ShadyCigs vending machine. + +ent-CrateVendingMachineRestockSnacksFilled = Snack restock crate + .desc = Contains four restock boxes, each covering a different snack vendor. Mr. Chang's, Discount Dans, Robust Donuts, and Getmore Chocolate are featured on the advertisement. + +ent-CrateVendingMachineRestockTankDispenserFilled = Tank dispenser restock crate + .desc = Contains a restock box for an Engineering or Atmospherics tank dispenser. diff --git a/Resources/Locale/en-US/vending-machines/vending-machine-restock-component.ftl b/Resources/Locale/en-US/vending-machines/vending-machine-restock-component.ftl new file mode 100644 index 0000000000..3d1968604a --- /dev/null +++ b/Resources/Locale/en-US/vending-machines/vending-machine-restock-component.ftl @@ -0,0 +1,4 @@ +vending-machine-restock-invalid-inventory = { CAPITALIZE(THE($this)) } isn't the right package to restock { THE($target) }. +vending-machine-restock-needs-panel-open = { CAPITALIZE($target) } needs { POSS-ADJ($target) } maintenance panel opened first. +vending-machine-restock-start = { $user } starts restocking { $target }. +vending-machine-restock-done = { $user } finishes restocking { $target }. diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml new file mode 100644 index 0000000000..17ff47f872 --- /dev/null +++ b/Resources/Prototypes/Catalog/Cargo/cargo_vending.yml @@ -0,0 +1,167 @@ +# If you've arrived here after a failed NoCargoOrderArbitrage test, it's likely +# because the product in question contains a restock box which links to a +# vending machine inventory that has had items added to it recently and caused +# its calculated price to exceed the cost below. +# +# The costs will need to be kept in line with the inventory of the respective +# vending machines. An extra hundred or two profit margin should be fine. + +- type: cargoProduct + id: CrateVendingMachineRestockBooze + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockBoozeFilled + cost: 3200 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockClothes + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockClothesFilled + cost: 4000 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockDinnerware + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockDinnerwareFilled + cost: 2000 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockEngineering + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockEngineeringFilled + cost: 3000 + category: Engineering + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockGames + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockGamesFilled + cost: 650 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockHotDrinks + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockHotDrinksFilled + cost: 1200 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockMedical + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockMedicalFilled + cost: 1500 + category: Medical + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockNutriMax + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockNutriMaxFilled + cost: 2400 + category: Hydroponics + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockPTech + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockPTechFilled + cost: 800 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockRobustSoftdrinks + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockRobustSoftdrinksFilled + cost: 1200 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockSalvageEquipment + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockSalvageEquipmentFilled + cost: 1000 + category: Engineering + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockSecTech + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockSecTechFilled + cost: 2200 + category: Security + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockSeeds + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockSeedsFilled + cost: 2000 + category: Hydroponics + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockSmokes + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockSmokesFilled + cost: 1200 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockSnacks + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockSnacksFilled + cost: 3000 + category: Service + group: market + +- type: cargoProduct + id: CrateVendingMachineRestockTankDispenser + icon: + sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + product: CrateVendingMachineRestockTankDispenserFilled + cost: 1000 + category: Atmospherics + group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/vending.yml b/Resources/Prototypes/Catalog/Fills/Crates/vending.yml new file mode 100644 index 0000000000..ed0a7519b0 --- /dev/null +++ b/Resources/Prototypes/Catalog/Fills/Crates/vending.yml @@ -0,0 +1,134 @@ +- type: entity + id: CrateVendingMachineRestockBoozeFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockBooze + +- type: entity + id: CrateVendingMachineRestockClothesFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockClothes + - id: VendingMachineRestockCostumes + +- type: entity + id: CrateVendingMachineRestockDinnerwareFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockDinnerware + +- type: entity + id: CrateVendingMachineRestockEngineeringFilled + parent: CrateEngineeringSecure + components: + - type: StorageFill + contents: + - id: VendingMachineRestockEngineering + +- type: entity + id: CrateVendingMachineRestockGamesFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockGames + +- type: entity + id: CrateVendingMachineRestockHotDrinksFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockHotDrinks + amount: 2 + +- type: entity + id: CrateVendingMachineRestockMedicalFilled + parent: CrateMedicalSecure + components: + - type: StorageFill + contents: + - id: VendingMachineRestockMedical + +- type: entity + id: CrateVendingMachineRestockNutriMaxFilled + parent: CrateHydroSecure + components: + - type: StorageFill + contents: + - id: VendingMachineRestockNutriMax + +- type: entity + id: CrateVendingMachineRestockPTechFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockPTech + +- type: entity + id: CrateVendingMachineRestockRobustSoftdrinksFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockRobustSoftdrinks + amount: 2 + +- type: entity + id: CrateVendingMachineRestockSalvageEquipmentFilled + parent: CrateGenericSteel + components: + - type: StorageFill + contents: + - id: VendingMachineRestockSalvageEquipment + +- type: entity + id: CrateVendingMachineRestockSecTechFilled + parent: CrateSecgear + components: + - type: StorageFill + contents: + - id: VendingMachineRestockSecTech + +- type: entity + id: CrateVendingMachineRestockSeedsFilled + parent: CrateHydroSecure + components: + - type: StorageFill + contents: + - id: VendingMachineRestockSeeds + +- type: entity + id: CrateVendingMachineRestockSmokesFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockSmokes + amount: 2 + +- type: entity + id: CrateVendingMachineRestockSnacksFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockChang + - id: VendingMachineRestockDiscountDans + - id: VendingMachineRestockDonut + - id: VendingMachineRestockGetmoreChocolateCorp + +- type: entity + id: CrateVendingMachineRestockTankDispenserFilled + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: VendingMachineRestockTankDispenser diff --git a/Resources/Prototypes/Entities/Markers/Spawners/vending_machine_restock.yml b/Resources/Prototypes/Entities/Markers/Spawners/vending_machine_restock.yml new file mode 100644 index 0000000000..f323d2268d --- /dev/null +++ b/Resources/Prototypes/Entities/Markers/Spawners/vending_machine_restock.yml @@ -0,0 +1,59 @@ +- type: entity + id: SpawnVendingMachineRestockFoodDrink + name: Vending Machine Restock + suffix: "food or drink" + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: refill_discount + - type: ConditionalSpawner + prototypes: + - VendingMachineRestockDiscountDans + - VendingMachineRestockChang + - VendingMachineRestockDonut + - VendingMachineRestockGetmoreChocolateCorp + - VendingMachineRestockRobustSoftdrinks + - VendingMachineRestockHotDrinks + +- type: entity + id: SpawnVendingMachineRestockFood + name: Vending Machine Restock + suffix: "food" + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: refill_donuts + - type: ConditionalSpawner + prototypes: + - VendingMachineRestockDiscountDans + - VendingMachineRestockChang + - VendingMachineRestockDonut + - VendingMachineRestockGetmoreChocolateCorp + +- type: entity + id: SpawnVendingMachineRestockDrink + name: Vending Machine Restock + suffix: "drink" + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: base + - sprite: Objects/Specific/Service/vending_machine_restock.rsi + state: refill_cola + - type: ConditionalSpawner + prototypes: + - VendingMachineRestockRobustSoftdrinks + - VendingMachineRestockHotDrinks diff --git a/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml b/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml new file mode 100644 index 0000000000..4e7a59e2ec --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Service/vending_machine_restock.yml @@ -0,0 +1,365 @@ +- type: entity + parent: BaseItem + id: BaseVendingMachineRestock + abstract: true + name: vending machine restock box + description: A box for restocking vending machines with corporate goodies. + components: + - type: VendingMachineRestock + - type: Sprite + netsync: false + sprite: Objects/Specific/Service/vending_machine_restock.rsi + layers: + - state: base + - state: green_bit + shader: unshaded + - type: ItemCooldown + - type: MeleeWeapon + damage: + types: + Blunt: 5 + soundHit: + path: /Audio/Weapons/genhit2.ogg + soundSwing: + path: /Audio/Weapons/punchmiss.ogg + - type: Item + size: 50 + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 20 + behaviors: + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/metalbreak.ogg + - !type:DumpRestockInventory + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockBooze + name: Booze-O-Mat restock box + description: Slot into your Booze-O-Mat to start the party! Not for sale to passengers below the legal age. + components: + - type: VendingMachineRestock + canRestock: + - BoozeOMatInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_booze + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockChang + name: Mr. Chang's restock box + description: A box covered in white labels with bold red Chinese characters, ready to be loaded into the nearest Mr. Chang's vending machine. + components: + - type: VendingMachineRestock + canRestock: + - ChangInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_chinese + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockClothes + name: ClothesMate restock box + description: It's time to step up your fashion! Place inside the ClothesMate restock slot to begin. + components: + - type: VendingMachineRestock + canRestock: + - ClothesMateInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_clothes + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockCostumes + name: AutoDrobe restock box + description: A panoply of NanoTrasen employees are prancing about a colorful theater in a tragicomedy. You can join them too! Load this into your nearest AutoDrobe vending machine. + components: + - type: VendingMachineRestock + canRestock: + - AutoDrobeInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_costume + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockDinnerware + name: Plasteel Chef's restock box + description: It's never raw in this kitchen! Drop into the restock slot on the Plasteel Chef to begin. + components: + - type: VendingMachineRestock + canRestock: + - DinnerwareInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_dinner + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockDiscountDans + name: Discount Dan's restock box + description: A box full of salt and starch. Why suffer Quality when you can have Quantity? Discount Dan's! + components: + - type: VendingMachineRestock + canRestock: + - DiscountDansInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_discount + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockDonut + name: Robust Donuts box + description: A box full of toroidal bundles of fried dough for restocking a vending machine. Use only as directed by Robust Industries, LLC. + components: + - type: VendingMachineRestock + canRestock: + - DonutInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_donuts + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockEngineering + name: EngiVend resupply box + description: Only to be used by certified professionals. + components: + - type: VendingMachineRestock + canRestock: + - EngiVendInventory + - YouToolInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_engi + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockGames + name: Good Clean Fun restock box + description: It's time to roll for initiative, dice dragons! Load up at the Good Clean Fun vending machine! + components: + - type: VendingMachineRestock + canRestock: + - GoodCleanFunInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_games + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockGetmoreChocolateCorp + name: GetMore Chocolate restock box + description: A box loaded with the finest ersatz cacao. Only to be used in official Getmore Chocolate vending machines. + components: + - type: VendingMachineRestock + canRestock: + - GetmoreChocolateCorpInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_snack + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockHotDrinks + name: Solar's Best restock box + description: Toasty! For use in Solar's Best Hot Drinks or other affiliate vending machines. + components: + - type: VendingMachineRestock + canRestock: + - HotDrinksMachineInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_joe + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockMedical + name: NanoMed resupply box + description: Slot into your department's NanoMed or NanoMedPlus to dispense. Handle with care. + components: + - type: VendingMachineRestock + canRestock: + - NanoMedInventory + - NanoMedPlusInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_medical + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockNutriMax + name: NutriMax restock box + description: We'll make your thumbs green with our tools. Let's get to harvesting! Load into a NutriMax vending machine. + components: + - type: VendingMachineRestock + canRestock: + - NutriMaxInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_plant + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockPTech + name: PTech resupply box + description: All the bureaucracy you can handle, and more! Load into the PTech vending machine to get started. + components: + - type: VendingMachineRestock + canRestock: + - PTechInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_ptech + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockRobustSoftdrinks + name: Robust Softdrinks box + description: A cold, clunky container of colliding chilly cylinders. Use only as directed by Robust Industries, LLC. + components: + - type: VendingMachineRestock + canRestock: + - RobustSoftdrinksInventory + - BodaInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_cola + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockSecTech + name: SecTech resupply box + description: "Communists beware: the reinforcements have arrived! A label reads SECURITY PERSONNEL ONLY." + components: + - type: VendingMachineRestock + canRestock: + - SecTechInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_sec + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockSalvageEquipment + name: salvage resupply box + description: Strike the earth ere the space carp nip your behind! Slam into a salvage vendor to begin. + components: + - type: VendingMachineRestock + canRestock: + - SalvageEquipmentInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_salvage + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockSeeds + name: MegaSeed restock box + description: A label says they're heirloom seeds, passed down from our ancestors. Pack it into the MegaSeed Servitor! + components: + - type: VendingMachineRestock + canRestock: + - MegaSeedServitorInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_plant + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockSmokes + name: ShadyCigs restock box + description: It's hard to see anything under all the Surgeon General warnings, but it mentions loading it into a vending machine. + components: + - type: VendingMachineRestock + canRestock: + - CigaretteMachineInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_smoke + +- type: entity + parent: BaseVendingMachineRestock + id: VendingMachineRestockTankDispenser + name: tank dispenser resupply box + description: Capable of replacing tanks in a gas tank dispenser. Handle with care. + components: + - type: VendingMachineRestock + canRestock: + - TankDispenserEVAInventory + - TankDispenserEngineeringInventory + - type: Sprite + layers: + - state: base + - state: green_bit + shader: unshaded + - state: refill_tanks diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/base.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/base.png new file mode 100644 index 0000000000..01a8c4ed21 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/base.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/green_bit.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/green_bit.png new file mode 100644 index 0000000000..e362ff1715 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/green_bit.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-left.png new file mode 100644 index 0000000000..0c543aa075 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-right.png new file mode 100644 index 0000000000..599ede72c1 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/meta.json b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/meta.json new file mode 100644 index 0000000000..e218106d63 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/meta.json @@ -0,0 +1,91 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Original source: https://github.com/tgstation/tgstation/blob/master/icons/obj/vending_restock.dmi @ commit 014c44ef6279beb02a5f3e76824439fa57181c22 - Additions and cleanup by @Vordenburg", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "green_bit" + }, + { + "name": "refill_booze" + }, + { + "name": "refill_chinese" + }, + { + "name": "refill_clothes" + }, + { + "name": "refill_cola" + }, + { + "name": "refill_costume" + }, + { + "name": "refill_custom" + }, + { + "name": "refill_dinner" + }, + { + "name": "refill_discount" + }, + { + "name": "refill_donksoft" + }, + { + "name": "refill_donuts" + }, + { + "name": "refill_engi" + }, + { + "name": "refill_games" + }, + { + "name": "refill_joe" + }, + { + "name": "refill_medical" + }, + { + "name": "refill_parts" + }, + { + "name": "refill_plant" + }, + { + "name": "refill_ptech" + }, + { + "name": "refill_salvage" + }, + { + "name": "refill_sec" + }, + { + "name": "refill_smoke" + }, + { + "name": "refill_snack" + }, + { + "name": "refill_tanks" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_booze.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_booze.png new file mode 100644 index 0000000000..f94fc84281 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_booze.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_chinese.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_chinese.png new file mode 100644 index 0000000000..96caec14e3 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_chinese.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_clothes.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_clothes.png new file mode 100644 index 0000000000..0000af1454 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_clothes.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_cola.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_cola.png new file mode 100644 index 0000000000..847a7994cd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_cola.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_costume.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_costume.png new file mode 100644 index 0000000000..01f43e7e14 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_costume.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_custom.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_custom.png new file mode 100644 index 0000000000..99ec2140dd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_custom.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_dinner.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_dinner.png new file mode 100644 index 0000000000..b44af8677f Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_dinner.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_discount.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_discount.png new file mode 100644 index 0000000000..710ff5b29d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_discount.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donksoft.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donksoft.png new file mode 100644 index 0000000000..0332b22a93 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donksoft.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donuts.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donuts.png new file mode 100644 index 0000000000..95fa792ea3 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_donuts.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_engi.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_engi.png new file mode 100644 index 0000000000..7b023bb67d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_engi.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_games.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_games.png new file mode 100644 index 0000000000..f1277d030d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_games.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_joe.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_joe.png new file mode 100644 index 0000000000..beade28ef2 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_joe.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_medical.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_medical.png new file mode 100644 index 0000000000..07c186166d Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_medical.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_parts.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_parts.png new file mode 100644 index 0000000000..7abff08bfd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_parts.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_plant.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_plant.png new file mode 100644 index 0000000000..6c95701b65 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_plant.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_ptech.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_ptech.png new file mode 100644 index 0000000000..d33e43c66e Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_ptech.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_salvage.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_salvage.png new file mode 100644 index 0000000000..0c428413a9 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_salvage.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_sec.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_sec.png new file mode 100644 index 0000000000..be0bf1722f Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_sec.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_smoke.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_smoke.png new file mode 100644 index 0000000000..ae5d30aacd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_smoke.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_snack.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_snack.png new file mode 100644 index 0000000000..088c3cec60 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_snack.png differ diff --git a/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_tanks.png b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_tanks.png new file mode 100644 index 0000000000..8ce2aedaf8 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Service/vending_machine_restock.rsi/refill_tanks.png differ