]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add limited-reagent dispensers (#23907)
authorKevin Zheng <kevinz5000@gmail.com>
Wed, 17 Jan 2024 21:43:48 +0000 (13:43 -0800)
committerGitHub <noreply@github.com>
Wed, 17 Jan 2024 21:43:48 +0000 (16:43 -0500)
* Add limited-reagent dispensers

* Add empty versions for all dispensers

* Fix lint

* Set initial window size so all buttons are visible

* Simplify logic, add parenthesis

* Use localized name for initial labels

* Adjust button style

* Avoid touching items before MapInit

* Remove pre-labeling

* Reduce diff

* Clean up YAML

* Fix test

* Really fix test

* Document

* Adjust based on review

* Add labels for obnoxiously long bottles

---------

Co-authored-by: AWF <you@example.com>
19 files changed:
Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs
Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs
Content.Shared/Chemistry/Dispenser/ReagentDispenserInventoryPrototype.cs
Content.Shared/Chemistry/SharedReagentDispenser.cs
Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml
Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml
Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml
Resources/Prototypes/Entities/Structures/Dispensers/booze.yml
Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
Resources/Prototypes/Entities/Structures/Dispensers/soda.yml
Resources/Prototypes/tags.yml

index c94759a6c1f5c55efcb26090a1d19d605b7f28d6..8244e3e6edb31ec0a0f991951fbad7fe73a6fbe5 100644 (file)
@@ -57,13 +57,23 @@ namespace Content.Client.Chemistry.UI
             _window.OnDispenseReagentButtonMouseEntered += (args, button) =>
             {
                 if (_lastState is not null)
-                    _window.UpdateContainerInfo(_lastState, button.ReagentId);
+                    _window.UpdateContainerInfo(_lastState);
             };
             _window.OnDispenseReagentButtonMouseExited += (args, button) =>
             {
                 if (_lastState is not null)
                     _window.UpdateContainerInfo(_lastState);
             };
+
+            _window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId));
+            _window.OnEjectJugButtonMouseEntered += (args, button) => {
+                if (_lastState is not null)
+                    _window.UpdateContainerInfo(_lastState);
+            };
+            _window.OnEjectJugButtonMouseExited += (args, button) => {
+                if (_lastState is not null)
+                    _window.UpdateContainerInfo(_lastState);
+            };
         }
 
         /// <summary>
index e17586db14ec5fce13dec08f6b450486d6e3c1ef..d9e480f132b8ee063f51a164716cb5847f7a70aa 100644 (file)
@@ -1,8 +1,7 @@
-<DefaultWindow xmlns="https://spacestation14.io"
+<DefaultWindow xmlns="https://spacestation14.io"
             xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
             Title="{Loc 'reagent-dispenser-bound-user-interface-title'}"
-            SetSize="620 450"
-            MinSize="620 450">
+            MinSize="680 450">
     <BoxContainer Orientation="Vertical">
         <BoxContainer Orientation="Horizontal">
             <Label Text="{Loc 'reagent-dispenser-window-amount-to-dispense-label'}"/>
             <Button Name="DispenseButton100" Access="Public" Text="100" StyleClasses="OpenLeft"/>
         </BoxContainer>
         <Control MinSize="0 10"/>
-        <ScrollContainer HScrollEnabled="False" HorizontalExpand="True" MinSize="0 170">
-            <GridContainer Name="ChemicalList" Access="Public" Columns="4">
-            </GridContainer>
-        </ScrollContainer>
+        <GridContainer Name="ChemicalList" HorizontalExpand="True" VerticalExpand="True" Access="Public" Columns="6">
+        </GridContainer>
         <Control MinSize="0 10"/>
         <BoxContainer Orientation="Horizontal">
             <Label Text="{Loc 'reagent-dispenser-window-container-label'}"/>
index bf08d4978e9af600501e0712b8d63229405889e7..332de34345c80721dee8b7356fe54328275ffdfc 100644 (file)
@@ -23,6 +23,10 @@ namespace Content.Client.Chemistry.UI
         public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseEntered;
         public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseExited;
 
+        public event Action<BaseButton.ButtonEventArgs, EjectJugButton>? OnEjectJugButtonPressed;
+        public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseEntered;
+        public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseExited;
+
         /// <summary>
         /// Create and initialize the dispenser UI client-side. Creates the basic layout,
         /// actual data isn't filled in until the server sends data about the dispenser.
@@ -48,25 +52,25 @@ namespace Content.Client.Chemistry.UI
         /// Update the button grid of reagents which can be dispensed.
         /// </summary>
         /// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
-        public void UpdateReagentsList(List<ReagentId> inventory)
+        public void UpdateReagentsList(List<KeyValuePair<string, KeyValuePair<string,string>>> inventory)
         {
             if (ChemicalList == null)
                 return;
 
             ChemicalList.Children.Clear();
 
-            foreach (var entry in inventory
-                .OrderBy(r => {_prototypeManager.TryIndex(r.Prototype, out ReagentPrototype? p); return p?.LocalizedName;}))
+            foreach (KeyValuePair<string, KeyValuePair<string, string>> entry in inventory)
             {
-                var localizedName = _prototypeManager.TryIndex(entry.Prototype, out ReagentPrototype? p)
-                    ? p.LocalizedName
-                    : Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
-
-                var button = new DispenseReagentButton(entry, localizedName);
+                var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value);
                 button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
                 button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
                 button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
                 ChemicalList.AddChild(button);
+                var ejectButton = new EjectJugButton(entry.Key);
+                ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton);
+                ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton);
+                ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton);
+                ChemicalList.AddChild(ejectButton);
             }
         }
 
@@ -121,9 +125,8 @@ namespace Content.Client.Chemistry.UI
         /// <para>Also highlights a reagent if it's dispense button is being mouse hovered.</para>
         /// </summary>
         /// <param name="state">State data for the dispenser.</param>
-        /// <param name="highlightedReagentId">Prototype ID of the reagent whose dispense button is currently being mouse hovered,
         /// or null if no button is being hovered.</param>
-        public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, ReagentId? highlightedReagentId = null)
+        public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state)
         {
             ContainerInfo.Children.Clear();
 
@@ -161,12 +164,6 @@ namespace Content.Client.Chemistry.UI
                     StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
                 };
 
-                // Check if the reagent is being moused over. If so, color it green.
-                if (reagent == highlightedReagentId) {
-                    nameLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
-                    quantityLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
-                }
-
                 ContainerInfo.Children.Add(new BoxContainer
                 {
                     Orientation = LayoutOrientation.Horizontal,
@@ -180,13 +177,27 @@ namespace Content.Client.Chemistry.UI
         }
     }
 
-    public sealed class DispenseReagentButton : Button {
-        public ReagentId ReagentId { get; }
+    public sealed class DispenseReagentButton : Button
+    {
+        public string ReagentId { get; }
+
+        public DispenseReagentButton(string reagentId, string text, string amount)
+        {
+            AddStyleClass("OpenRight");
+            ReagentId = reagentId;
+            Text = text + " " + amount;
+        }
+    }
+
+    public sealed class EjectJugButton : Button
+    {
+        public string ReagentId { get; }
 
-        public DispenseReagentButton(ReagentId reagentId, string text)
+        public EjectJugButton(string reagentId)
         {
+            AddStyleClass("OpenLeft");
             ReagentId = reagentId;
-            Text = text;
+            Text = "⏏";
         }
     }
 }
index 13a0bee28e7561f88900034e7a0411053581b00a..98bb7da6b77d9470dcca1d395d80b8fdc17cc965 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Client.Chemistry.UI;
 using Content.IntegrationTests.Tests.Interaction;
 using Content.Shared.Chemistry;
+using Content.Server.Chemistry.Components;
 using Content.Shared.Containers.ItemSlots;
 
 namespace Content.IntegrationTests.Tests.Chemistry;
@@ -24,7 +25,7 @@ public sealed class DispenserTest : InteractionTest
         await Interact();
 
         // Eject beaker via BUI.
-        var ev = new ItemSlotButtonPressedEvent(SharedChemMaster.InputSlotName);
+        var ev = new ItemSlotButtonPressedEvent(ReagentDispenserComponent.BeakerSlotId);
         await SendBui(ReagentDispenserUiKey.Key, ev);
 
         // Beaker is back in the player's hands
index 7229010228b4e2f28ffab6865d05fa77be2fe0fa..4cf0d2e29e0517a0c7f3a1c10ffb045fcc5841d7 100644 (file)
@@ -1,3 +1,5 @@
+using Content.Shared.Whitelist;
+using Content.Shared.Containers.ItemSlots;
 using Content.Server.Chemistry.EntitySystems;
 using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Dispenser;
@@ -7,20 +9,57 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
 namespace Content.Server.Chemistry.Components
 {
     /// <summary>
-    /// A machine that dispenses reagents into a solution container.
+    /// A machine that dispenses reagents into a solution container from containers in its storage slots.
     /// </summary>
     [RegisterComponent]
     [Access(typeof(ReagentDispenserSystem))]
     public sealed partial class ReagentDispenserComponent : Component
     {
-
+        /// <summary>
+        /// String with the pack name that stores the initial fill of the dispenser. The initial
+        /// fill is added to the dispenser on MapInit. Note that we don't use ContainerFill because
+        /// we have to generate the storage slots at MapInit first, then fill them.
+        /// </summary>
         [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
         [ViewVariables(VVAccess.ReadWrite)]
         public string? PackPrototypeId = default!;
 
-        [DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public string? EmagPackPrototypeId = default!;
+        /// <summary>
+        /// Maximum number of internal storage slots. Dispenser can't store (or dispense) more than
+        /// this many chemicals (without unloading and reloading).
+        /// </summary>
+        [DataField("numStorageSlots")]
+        public int NumSlots = 25;
+
+        /// <summary>
+        /// For each created storage slot for the reagent containers being dispensed, apply this
+        /// entity whitelist. Makes sure weird containers don't fit in the dispenser and that beakers
+        /// don't accidentally get slotted into the source slots.
+        /// </summary>
+        [DataField]
+        public EntityWhitelist? StorageWhitelist;
+
+        /// <summary>
+        /// Slot for container to dispense into.
+        /// </summary>
+        public static string BeakerSlotId = "ReagentDispenser-beakerSlot";
+
+        [DataField]
+        public ItemSlot BeakerSlot = new();
+
+        /// <summary>
+        /// Prefix for automatically-generated slot name for storage, up to NumSlots.
+        /// </summary>
+        public static string BaseStorageSlotId = "ReagentDispenser-storageSlot";
+
+        /// <summary>
+        /// List of storage slots that were created at MapInit.
+        /// </summary>
+        [DataField]
+        public List<string> StorageSlotIds = new List<string>();
+
+        [DataField]
+        public List<ItemSlot> StorageSlots = new List<ItemSlot>();
 
         [DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
         public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
index aeb141fe35b8b365a0788bac55f1b95850011801..a0d6a66bb3460b27f89833fa187ddd8072aec778 100644 (file)
@@ -1,14 +1,18 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Chemistry.Components;
 using Content.Server.Chemistry.Containers.EntitySystems;
+using Content.Server.Nutrition.Components;
+using Content.Server.Nutrition.EntitySystems;
+using Content.Server.Labels.Components;
+using Content.Server.Chemistry;
 using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.Chemistry.Dispenser;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Containers.ItemSlots;
 using Content.Shared.Database;
-using Content.Shared.Emag.Components;
-using Content.Shared.Emag.Systems;
+using Content.Shared.FixedPoint;
 using JetBrains.Annotations;
 using Robust.Server.Audio;
 using Robust.Server.GameObjects;
@@ -28,10 +32,13 @@ namespace Content.Server.Chemistry.EntitySystems
     {
         [Dependency] private readonly AudioSystem _audioSystem = default!;
         [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
+        [Dependency] private readonly SolutionTransferSystem _solutionTransferSystem = default!;
         [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+        [Dependency] private readonly OpenableSystem _openable = default!;
+
         public override void Initialize()
         {
             base.Initialize();
@@ -41,11 +48,12 @@ namespace Content.Server.Chemistry.EntitySystems
             SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
             SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
             SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
-            SubscribeLocalEvent<ReagentDispenserComponent, GotEmaggedEvent>(OnEmagged);
 
             SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
             SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
             SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
+
+            SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
         }
 
         private void SubscribeUpdateUiState<T>(Entity<ReagentDispenserComponent> ent, ref T ev)
@@ -80,35 +88,38 @@ namespace Content.Server.Chemistry.EntitySystems
             return null;
         }
 
-        private List<ReagentId> GetInventory(Entity<ReagentDispenserComponent> ent)
+        private List<KeyValuePair<string, KeyValuePair<string, string>>> GetInventory(ReagentDispenserComponent reagentDispenser)
         {
-            var reagentDispenser = ent.Comp;
-            var inventory = new List<ReagentId>();
+            var inventory = new List<KeyValuePair<string, KeyValuePair<string, string>>>();
 
-            if (reagentDispenser.PackPrototypeId is not null
-                && _prototypeManager.TryIndex(reagentDispenser.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
+            for (var i = 0; i < reagentDispenser.NumSlots; i++)
             {
-                inventory.AddRange(packPrototype.Inventory.Select(x => new ReagentId(x, null)));
-            }
+                var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
+                var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, storageSlotId);
+
+                // Set label from manually-applied label, or metadata if unavailable
+                string reagentLabel;
+                if (TryComp<LabelComponent>(storedContainer, out var label) && !string.IsNullOrEmpty(label.CurrentLabel))
+                    reagentLabel = label.CurrentLabel;
+                else if (storedContainer != null)
+                    reagentLabel = Name(storedContainer.Value);
+                else
+                    continue;
+
+                // Add volume remaining label
+                FixedPoint2 quantity = 0f;
+                if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol))
+                {
+                    quantity = sol.Volume;
+                }
+                var storedAmount = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity));
 
-            if (HasComp<EmaggedComponent>(ent)
-                && reagentDispenser.EmagPackPrototypeId is not null
-                && _prototypeManager.TryIndex(reagentDispenser.EmagPackPrototypeId, out ReagentDispenserInventoryPrototype? emagPackPrototype))
-            {
-                inventory.AddRange(emagPackPrototype.Inventory.Select(x => new ReagentId(x, null)));
+                inventory.Add(new KeyValuePair<string, KeyValuePair<string, string>>(storageSlotId, new KeyValuePair<string, string>(reagentLabel, storedAmount)));
             }
 
             return inventory;
         }
 
-        private void OnEmagged(Entity<ReagentDispenserComponent> reagentDispenser, ref GotEmaggedEvent args)
-        {
-            // adding component manually to have correct state
-            EntityManager.AddComponent<EmaggedComponent>(reagentDispenser);
-            UpdateUiState(reagentDispenser);
-            args.Handled = true;
-        }
-
         private void OnSetDispenseAmountMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserSetDispenseAmountMessage message)
         {
             reagentDispenser.Comp.DispenseAmount = message.ReagentDispenserDispenseAmount;
@@ -119,18 +130,23 @@ namespace Content.Server.Chemistry.EntitySystems
         private void OnDispenseReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserDispenseReagentMessage message)
         {
             // Ensure that the reagent is something this reagent dispenser can dispense.
-            if (!GetInventory(reagentDispenser).Contains(message.ReagentId))
+            var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, message.SlotId);
+            if (storedContainer == null)
                 return;
 
             var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
             if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
                 return;
 
-            if (_solutionContainerSystem.TryAddReagent(solution.Value, message.ReagentId, (int) reagentDispenser.Comp.DispenseAmount, out var dispensedAmount)
-                && message.Session.AttachedEntity is not null)
+            if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out var src, out _) &&
+                _solutionContainerSystem.TryGetRefillableSolution(outputContainer.Value, out var dst, out _))
             {
-                _adminLogger.Add(LogType.ChemicalReaction, LogImpact.Medium,
-                    $"{ToPrettyString(message.Session.AttachedEntity.Value):player} dispensed {dispensedAmount}u of {message.ReagentId} into {ToPrettyString(outputContainer.Value):entity}");
+                // force open container, if applicable, to avoid confusing people on why it doesn't dispense
+                _openable.SetOpen(storedContainer.Value, true);
+                _solutionTransferSystem.Transfer(reagentDispenser,
+                        storedContainer.Value, src.Value,
+                        outputContainer.Value, dst.Value,
+                        (int)reagentDispenser.Comp.DispenseAmount);
             }
 
             UpdateUiState(reagentDispenser);
@@ -152,5 +168,41 @@ namespace Content.Server.Chemistry.EntitySystems
         {
             _audioSystem.PlayPvs(reagentDispenser.Comp.ClickSound, reagentDispenser, AudioParams.Default.WithVolume(-2f));
         }
+
+        /// <summary>
+        /// Automatically generate storage slots for all NumSlots, and fill them with their initial chemicals.
+        /// The actual spawning of entities happens in ItemSlotsSystem's MapInit.
+        /// </summary>
+        private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
+        {
+            // Get list of pre-loaded containers
+            List<string> preLoad = new List<string>();
+            if (component.PackPrototypeId is not null
+                && _prototypeManager.TryIndex(component.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
+            {
+                preLoad.AddRange(packPrototype.Inventory);
+            }
+
+            // Populate storage slots with base storage slot whitelist
+            for (var i = 0; i < component.NumSlots; i++)
+            {
+                var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
+                ItemSlot storageComponent = new();
+                storageComponent.Whitelist = component.StorageWhitelist;
+                storageComponent.Swap = false;
+                storageComponent.EjectOnBreak = true;
+
+                // Check corresponding index in pre-loaded container (if exists) and set starting item
+                if (i < preLoad.Count)
+                    storageComponent.StartingItem = preLoad[i];
+
+                component.StorageSlotIds.Add(storageSlotId);
+                component.StorageSlots.Add(storageComponent);
+                component.StorageSlots[i].Name = "Storage Slot " + (i+1);
+                _itemSlotsSystem.AddItemSlot(uid, component.StorageSlotIds[i], component.StorageSlots[i]);
+            }
+
+            _itemSlotsSystem.AddItemSlot(uid, ReagentDispenserComponent.BeakerSlotId, component.BeakerSlot);
+        }
     }
 }
index c362535e4f585ee6d9b20526b5a3809bbaeebb34..5cdc8aed80f6503819db25377f7d3c31351396ce 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Chemistry.Reagent;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
@@ -14,8 +14,7 @@ namespace Content.Shared.Chemistry.Dispenser
     [Serializable, NetSerializable, Prototype("reagentDispenserInventory")]
     public sealed partial class ReagentDispenserInventoryPrototype : IPrototype
     {
-        // TODO use ReagentId
-        [DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
+        [DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
         public List<string> Inventory = new();
 
         [ViewVariables, IdDataField]
index 1ecb0993f535c1a8f0a6228600196a64fb3261da..be71bf8a7765ee012f40d1915d3f31a5f25b8cd9 100644 (file)
@@ -8,7 +8,7 @@ namespace Content.Shared.Chemistry
     /// </summary>
     public sealed class SharedReagentDispenser
     {
-        public const string OutputSlotName = "beakerSlot";
+        public const string OutputSlotName = "ReagentDispenser-beakerSlot";
     }
 
     [Serializable, NetSerializable]
@@ -25,11 +25,11 @@ namespace Content.Shared.Chemistry
     [Serializable, NetSerializable]
     public sealed class ReagentDispenserDispenseReagentMessage : BoundUserInterfaceMessage
     {
-        public readonly ReagentId ReagentId;
+        public readonly string SlotId;
 
-        public ReagentDispenserDispenseReagentMessage(ReagentId reagentId)
+        public ReagentDispenserDispenseReagentMessage(string slotId)
         {
-            ReagentId = reagentId;
+            SlotId = slotId;
         }
     }
 
@@ -59,11 +59,11 @@ namespace Content.Shared.Chemistry
         /// <summary>
         /// A list of the reagents which this dispenser can dispense.
         /// </summary>
-        public readonly List<ReagentId> Inventory;
+        public readonly List<KeyValuePair<string, KeyValuePair<string, string>>> Inventory;
 
         public readonly ReagentDispenserDispenseAmount SelectedDispenseAmount;
 
-        public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List<ReagentId> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
+        public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List<KeyValuePair<string, KeyValuePair<string, string>>> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
         {
             OutputContainer = outputContainer;
             Inventory = inventory;
index 9310617634de31875c4330e70e7121f8a2c99cca..f02cdc48dbb46d455c83ac39c01f852d93961e20 100644 (file)
@@ -67,8 +67,8 @@ namespace Content.Shared.Containers.ItemSlots
             CopyFrom(other);
         }
 
-
         [DataField("whitelist")]
+        [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
         public EntityWhitelist? Whitelist;
 
         [DataField("blacklist")]
@@ -179,6 +179,7 @@ namespace Content.Shared.Containers.ItemSlots
         ///     The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem.
         /// </remarks>
         [DataField("ejectOnDeconstruct")]
+        [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
         [NonSerialized]
         public bool EjectOnDeconstruct = true;
 
@@ -187,6 +188,7 @@ namespace Content.Shared.Containers.ItemSlots
         ///     ejected when it is broken or destroyed?
         /// </summary>
         [DataField("ejectOnBreak")]
+        [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
         [NonSerialized]
         public bool EjectOnBreak = false;
 
@@ -205,6 +207,7 @@ namespace Content.Shared.Containers.ItemSlots
         ///     want to insert more than one item that matches the same whitelist.
         /// </remarks>
         [DataField("swap")]
+        [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
         public bool Swap = true;
 
         public string? ID => ContainerSlot?.ID;
index 1af7f4cef64a9b14ba23b5eaa292d48be6ea1a3a..62b166ebc16295670cf105761e0d7dd4c09b3e3a 100644 (file)
@@ -1,61 +1,43 @@
 - type: reagentDispenserInventory
   id: SodaDispenserInventory
   inventory:
-  - Ice
-  - Coffee
-  - Cream
-  - Tea
-  - GreenTea
-  - IcedTea
-  - IcedGreenTea
-  - Cola
-  - SpaceMountainWind
-  - DrGibb
-  - RootBeer
-  - SpaceUp
-  - TonicWater
-  - SodaWater
-  - LemonLime
-  - Sugar
-  - JuiceOrange
-  - JuiceLime
-  - JuiceWatermelon
-  ###Hacked
-  #- Fourteen Loko
-  #- GrapeSoda
+  - DrinkIceJug
+  - DrinkCoffeeJug
+  - DrinkCreamCartonXL
+  - DrinkTeaJug
+  - DrinkGreenTeaJug
+  - DrinkIcedTeaJug
+  - DrinkColaBottleFull
+  - DrinkSpaceMountainWindBottleFull
+  - DrinkDrGibbJug
+  - DrinkRootBeerJug
+  - DrinkSpaceUpBottleFull
+  - DrinkTonicWaterBottleFull
+  - DrinkSodaWaterBottleFull
+  - DrinkLemonLimeJug
+  - DrinkSugarJug
+  - DrinkJuiceOrangeCartonXL
+  - DrinkJuiceLimeCartonXL
+  - DrinkWaterMelonJuiceJug
 
 - type: reagentDispenserInventory
   id: BoozeDispenserInventory
   inventory:
-  - Beer
-  - CoffeeLiqueur
-  - Whiskey
-  - Wine
-  - Vodka
-  - Gin
-  - Rum
-  - Tequila
-  - Vermouth
-  - Cognac
-  - Ale
-  - Mead
-  ###Hacked
-  #- Goldschlager
-  #- Patron
-  #- JuiceWatermelon
-  #- JuiceBerry
-
-
-- type: reagentDispenserInventory
-  id: SodaDispenserEmagInventory
-  inventory:
-    - FourteenLoko
-    - Ephedrine
-    - Histamine
-
-- type: reagentDispenserInventory
-  id: BoozeDispenserEmagInventory
-  inventory:
-    - AtomicBomb
-    - Ethanol
-    - Iron
+  - DrinkLemonLimeJug
+  - DrinkSugarJug
+  - DrinkJuiceOrangeCartonXL
+  - DrinkJuiceLimeCartonXL
+  - DrinkTonicWaterBottleFull
+  - DrinkSodaWaterBottleFull
+  - DrinkBeerGrowler
+  - DrinkCoffeeLiqueurBottleFull
+  - DrinkWhiskeyBottleFull
+  - DrinkWineBottleFull
+  - DrinkVodkaBottleFull
+  - DrinkGinBottleFull
+  - DrinkRumBottleFull
+  - DrinkTequilaBottleFull
+  - DrinkVermouthBottleFull
+  - DrinkCognacBottleFull
+  - DrinkAleBottleFullGrowler
+  - DrinkMeadJug
index 6d9c729fc933b4a8d77b122509f49b4beb43f81b..2b0fdfae6ce443a9aa0f31f4d4e886f2376b1ea9 100644 (file)
@@ -1,31 +1,26 @@
 - type: reagentDispenserInventory
   id: ChemDispenserStandardInventory
   inventory:
-  - Aluminium
-  - Carbon
-  - Chlorine
-  - Copper
-  - Ethanol
-  - Fluorine
-  - Sugar
-  - Hydrogen
-  - Iodine
-  - Iron
-  - Lithium
-  - Mercury
-  - Nitrogen
-  - Oxygen
-  - Phosphorus
-  - Potassium
-  - Radium
-  - Silicon
-  - Sodium
-  - Sulfur
+  - JugAluminium
+  - JugCarbon
+  - JugChlorine
+  - JugCopper
+  - JugEthanol
+  - JugFluorine
+  - JugSugar
+  - JugHydrogen
+  - JugIodine
+  - JugIron
+  - JugLithium
+  - JugMercury
+  - JugNitrogen
+  - JugOxygen
+  - JugPhosphorus
+  - JugPotassium
+  - JugRadium
+  - JugSilicon
+  - JugSodium
+  - JugSulfur
 
 - type: reagentDispenserInventory
-  id: ChemDispenserEmaggedInventory
-  inventory: ##Feel free to change this to something more interesting when more chems are added
-  - Napalm
-  - Toxin
-  - Epinephrine
-  - Ultravasculine
+  id: EmptyInventory
index 06dc47ce437f2615bf943fc3226558c8762b3836..7837ae4e76105c7cc0f45ff3a3e830c184b7946f 100644 (file)
@@ -4,6 +4,9 @@
   id: DrinkBottlePlasticBaseFull
   abstract: true
   components:
+  - type: Tag
+    tags:
+    - DrinkBottle
   - type: Openable
     sound:
       collection: bottleOpenSounds
@@ -13,6 +16,7 @@
         maxVol: 100
   - type: Sprite
     state: icon
+    sprite: Objects/Consumable/Drinks/water.rsi # fallback to boring water jug
   - type: Item
     size: Normal
   - type: Damageable
         reagents:
         - ReagentId: Rum
           Quantity: 100
+  - type: Label
+    currentLabel: rum
   - type: Sprite
     sprite: Objects/Consumable/Drinks/rumbottle.rsi
 
         reagents:
         - ReagentId: Tequila
           Quantity: 100
+  - type: Label
+    currentLabel: tequila
   - type: Sprite
     sprite: Objects/Consumable/Drinks/tequillabottle.rsi
 
         reagents:
         - ReagentId: Vermouth
           Quantity: 100
+  - type: Label
+    currentLabel: vermouth
   - type: Sprite
     sprite: Objects/Consumable/Drinks/vermouthbottle.rsi
 
         reagents:
         - ReagentId: Whiskey
           Quantity: 100
+  - type: Label
+    currentLabel: whiskey
   - type: Sprite
     sprite: Objects/Consumable/Drinks/whiskeybottle.rsi
 
         reagents:
         - ReagentId: Wine
           Quantity: 100
+  - type: Label
+    currentLabel: wine
   - type: Sprite
     sprite: Objects/Consumable/Drinks/winebottle.rsi
 
 
 - type: entity
   parent: DrinkBottleGlassBaseFull
+  id: DrinkBeerGrowler # Needs to be renamed DrinkBeerBottleFull
+  name: Beer Growler  # beer it is. coffee. beer? coff-ee? be-er? c-o... b-e
+  description: An alcoholic beverage made from malted grains, hops, yeast, and water. XL growler bottle.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: Beer
+          Quantity: 150
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/beer.rsi
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
   id: DrinkAleBottleFull
   name: Magm-Ale
   description: A true dorf's drink of choice.
   - type: Sprite
     sprite: Objects/Consumable/Drinks/alebottle.rsi
 
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkAleBottleFullGrowler
+  name: Magm-Ale Growler
+  description: A true dorf's drink of choice. XL growler bottle.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: Ale
+          Quantity: 150
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/alebottle.rsi
+
 - type: entity
   parent: DrinkBottlePlasticBaseFull
   id: DrinkWaterBottleFull
   - type: Drink
   - type: Sprite
     sprite: Objects/Consumable/Drinks/waterbottle.rsi
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkSodaWaterBottleFull
+  name: soda water bottle
+  description: Like water, but angry!
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: SodaWater
+          Quantity: 150
+  - type: Drink
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/waterbottle.rsi
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkTonicWaterBottleFull
+  name: tonic water bottle
+  description: Like soda water, but angrier maybe? Often sweeter.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: TonicWater
+          Quantity: 150
+  - type: Drink
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/waterbottle.rsi
+
+# Cartons, TODO: this needs to be moved elsewhere eventually, since cartons shouldnt smash into glass shards
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkJuiceLimeCartonXL
+  name: lime juice XL
+  description: Sweet-sour goodness.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: JuiceLime
+          Quantity: 150
+  - type: Drink
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/limejuice.rsi
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkJuiceOrangeCartonXL
+  name: orange juice XL
+  description: Full of vitamins and deliciousness!
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: JuiceOrange
+          Quantity: 150
+  - type: Drink
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/orangejuice.rsi
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkCreamCartonXL
+  name: Milk Cream XL
+  description: It's cream. Made from milk. What else did you think you'd find in there?
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: Cream
+          Quantity: 150
+  - type: Drink
+  - type: Sprite
+    sprite: Objects/Consumable/Drinks/cream.rsi
+
+#boring jugs some more sprites are made
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkSugarJug
+  name: sugar
+  suffix: for drinks
+  description: some people put this in their coffee...
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: Sugar
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkLemonLimeJug
+  name: lemon lime
+  description: a dual citrus sensation.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: LemonLime
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkMeadJug
+  name: mead jug
+  description: storing mead in a plastic jug should be a crime.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 150
+        reagents:
+        - ReagentId: Mead
+          Quantity: 150
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkIceJug
+  name: ice jug
+  description: stubborn water. pretty cool.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: Ice
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkCoffeeJug
+  name: coffee jug
+  description: wake up juice, of the heated kind.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: Coffee
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkTeaJug
+  name: tea jug
+  description: the drink of choice for the Bri'ish and hipsters.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: Tea
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkGreenTeaJug
+  name: green tea jug
+  description: its like tea... but green! great for settling the stomach.
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: GreenTea
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkIcedTeaJug
+  name: iced tea jug
+  description: for when the regular tea is too hot for you boohoo
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: IcedTea
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkDrGibbJug
+  name: dr gibb jug
+  description: yeah I don't know either...
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: DrGibb
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkRootBeerJug
+  name: root beer jug
+  description: this drink makes Australians giggle
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: RootBeer
+          Quantity: 300
+  - type: Drink
+
+- type: entity
+  parent: DrinkBottlePlasticBaseFull
+  id: DrinkWaterMelonJuiceJug
+  name: watermelon juice jug
+  description: May include leftover seeds
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      drink:
+        maxVol: 300
+        reagents:
+        - ReagentId: JuiceWatermelon
+          Quantity: 300
+  - type: Drink
index e175bbe728b98047027b5e4fe0e656e5920e306b..672d6488e356446a611758fc6bebf739f6a02253 100644 (file)
     - type: Sprite
       state: medical
     - type: MachineBoard
-      prototype: ChemDispenser
+      prototype: ChemDispenserEmpty
       requirements:
         Capacitor: 1
       materialRequirements:
     - type: Sprite
       state: service
     - type: MachineBoard
-      prototype: BoozeDispenser
+      prototype: BoozeDispenserEmpty
       materialRequirements:
         Steel: 5
       tagRequirements:
     - type: Sprite
       state: service
     - type: MachineBoard
-      prototype: soda_dispenser
+      prototype: SodaDispenserEmpty
       materialRequirements:
         Steel: 5
       tagRequirements:
index 139eecfe83c7741443c3f65688f1bb05b4e3d486..01f5e45c471a15b63a1c0580a910bd29d0c5fa73 100644 (file)
@@ -47,6 +47,9 @@
       price: 60
     - type: Label
       originalName: jug
+    - type: Tag
+      tags:
+      - ChemDispensable
 
 - type: entity
   parent: Jug
index e170ce825551809ae3dba9212a38b1329e7474c8..4ce88c15827cb5d9c0f99fac26bc84569a09cb95 100644 (file)
@@ -1,4 +1,4 @@
-- type: entity
+- type: entity
   abstract: true
   id: ReagentDispenserBase
   parent: ConstructibleMachine
         sound:
           path: /Audio/Effects/metalbreak.ogg
   - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - Bottle
+    beakerSlot:
+      whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message
+      whitelist:
+        components:
+        - FitsInDispenser
   - type: ItemSlots
-    slots:
-      beakerSlot:
-        whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message
-        whitelist:
-          components:
-          - FitsInDispenser
   - type: ContainerContainer
     containers:
       machine_board: !type:Container
       machine_parts: !type:Container
-      beakerSlot: !type:ContainerSlot
+      ReagentDispenser-beakerSlot: !type:ContainerSlot
   - type: StaticPrice
     price: 1000
   - type: Wires
index 15e40f79c35ca9d787ce7119d4cca8dfebb20138..1583bc451de64b3559809da4f7d2fca10634815f 100644 (file)
@@ -1,6 +1,7 @@
 - type: entity
   id: BoozeDispenser
   name: booze dispenser
+  suffix: Filled
   description: A booze dispenser with a single slot for a container to be filled.
   parent: ReagentDispenserBase
   components:
     drawdepth: SmallObjects
     state: booze
   - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - DrinkBottle
     pack: BoozeDispenserInventory
-    emagPack: BoozeDispenserEmagInventory
   - type: Transform
     noRot: false
   - type: Machine
     - Bartender
   - type: StealTarget
     stealGroup: BoozeDispenser
+
+- type: entity
+  id: BoozeDispenserEmpty
+  suffix: Empty
+  parent: BoozeDispenser
+  components:
+  - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - DrinkBottle
+    pack: EmptyInventory
index 916b2b748c8f45a36d88e0f99111b29f22a61b72..2d98b9ff35676c46343261e8e6b1a75dd704633b 100644 (file)
@@ -1,16 +1,19 @@
-- type: entity
+- type: entity
   id: ChemDispenser
   name: chemical dispenser
+  suffix: Filled
   parent: ReagentDispenserBase
-  description: An industrial grade chemical dispenser with a sizeable chemical supply.
+  description: An industrial grade chemical dispenser.
   components:
   - type: Sprite
     sprite: Structures/dispensers.rsi
     state: industrial-working
     snapCardinals: true
   - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - ChemDispensable
     pack: ChemDispenserStandardInventory
-    emagPack: ChemDispenserEmaggedInventory
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: Destructible
     - Chemist
   - type: StealTarget
     stealGroup: ChemDispenser
+
+- type: entity
+  id: ChemDispenserEmpty
+  name: chemical dispenser
+  suffix: Empty
+  parent: ChemDispenser
+  components:
+  - type: ReagentDispenser
+    pack: EmptyInventory
index 4322d56947a3a9247e0947da148dfd099076d34c..323480506fa1b1237f62cdb3b43d90a0af45c065 100644 (file)
@@ -1,6 +1,7 @@
 - type: entity
   id: soda_dispenser
   name: soda dispenser
+  suffix: Filled
   parent: ReagentDispenserBase
   description: A beverage dispenser with a selection of soda and several other common beverages. Has a single fill slot for containers.
   components:
     drawdepth: SmallObjects
     state: soda
   - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - DrinkBottle
     pack: SodaDispenserInventory
-    emagPack: SodaDispenserEmagInventory
   - type: Transform
     noRot: false
   - type: Machine
   - type: GuideHelp
     guides:
     - Bartender
+
+- type: entity
+  id: SodaDispenserEmpty
+  suffix: Empty
+  parent: soda_dispenser
+  components:
+  - type: ReagentDispenser
+    storageWhitelist:
+      tags:
+      - DrinkBottle
+    pack: EmptyInventory
index 88186ef84b7cb9b6b4d995f7dc08ca766d035d32..8fb58db7c1ea68ff3cb438e0bf41730486bc7586 100644 (file)
 - type: Tag
   id: Chicken
 
+- type: Tag
+  id: ChemDispensable # container that can go into the chem dispenser
+
 - type: Tag
   id: Cigarette
 
 - type: Tag
   id: DrinkSpaceGlue
 
+- type: Tag
+  id: DrinkBottle
+
 - type: Tag
   id: DroneUsable