diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
index c94759a6c1..8244e3e6ed 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
+++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs
@@ -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);
+ };
}
///
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
index e17586db14..d9e480f132 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
+++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml
@@ -1,8 +1,7 @@
-
+ MinSize="680 450">
@@ -18,10 +17,8 @@
-
-
-
-
+
+
diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
index bf08d4978e..332de34345 100644
--- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
+++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml.cs
@@ -23,6 +23,10 @@ namespace Content.Client.Chemistry.UI
public event Action? OnDispenseReagentButtonMouseEntered;
public event Action? OnDispenseReagentButtonMouseExited;
+ public event Action? OnEjectJugButtonPressed;
+ public event Action? OnEjectJugButtonMouseEntered;
+ public event Action? OnEjectJugButtonMouseExited;
+
///
/// 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.
///
/// Reagents which can be dispensed by this dispenser
- public void UpdateReagentsList(List inventory)
+ public void UpdateReagentsList(List>> 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> 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
/// Also highlights a reagent if it's dispense button is being mouse hovered.
///
/// State data for the dispenser.
- /// Prototype ID of the reagent whose dispense button is currently being mouse hovered,
/// or null if no button is being hovered.
- 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(ReagentId reagentId, string text)
+ public DispenseReagentButton(string reagentId, string text, string amount)
{
+ AddStyleClass("OpenRight");
ReagentId = reagentId;
- Text = text;
+ Text = text + " " + amount;
+ }
+ }
+
+ public sealed class EjectJugButton : Button
+ {
+ public string ReagentId { get; }
+
+ public EjectJugButton(string reagentId)
+ {
+ AddStyleClass("OpenLeft");
+ ReagentId = reagentId;
+ Text = "⏏";
}
}
}
diff --git a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs
index 13a0bee28e..98bb7da6b7 100644
--- a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs
+++ b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs
@@ -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
diff --git a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
index 7229010228..4cf0d2e29e 100644
--- a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
+++ b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs
@@ -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
{
///
- /// A machine that dispenses reagents into a solution container.
+ /// A machine that dispenses reagents into a solution container from containers in its storage slots.
///
[RegisterComponent]
[Access(typeof(ReagentDispenserSystem))]
public sealed partial class ReagentDispenserComponent : Component
{
-
+ ///
+ /// 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.
+ ///
[DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer))]
[ViewVariables(VVAccess.ReadWrite)]
public string? PackPrototypeId = default!;
- [DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer))]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? EmagPackPrototypeId = default!;
+ ///
+ /// Maximum number of internal storage slots. Dispenser can't store (or dispense) more than
+ /// this many chemicals (without unloading and reloading).
+ ///
+ [DataField("numStorageSlots")]
+ public int NumSlots = 25;
+
+ ///
+ /// 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.
+ ///
+ [DataField]
+ public EntityWhitelist? StorageWhitelist;
+
+ ///
+ /// Slot for container to dispense into.
+ ///
+ public static string BeakerSlotId = "ReagentDispenser-beakerSlot";
+
+ [DataField]
+ public ItemSlot BeakerSlot = new();
+
+ ///
+ /// Prefix for automatically-generated slot name for storage, up to NumSlots.
+ ///
+ public static string BaseStorageSlotId = "ReagentDispenser-storageSlot";
+
+ ///
+ /// List of storage slots that were created at MapInit.
+ ///
+ [DataField]
+ public List StorageSlotIds = new List();
+
+ [DataField]
+ public List StorageSlots = new List();
[DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs
index aeb141fe35..a0d6a66bb3 100644
--- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs
+++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs
@@ -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(SubscribeUpdateUiState);
SubscribeLocalEvent(SubscribeUpdateUiState);
SubscribeLocalEvent(SubscribeUpdateUiState);
- SubscribeLocalEvent(OnEmagged);
SubscribeLocalEvent(OnSetDispenseAmountMessage);
SubscribeLocalEvent(OnDispenseReagentMessage);
SubscribeLocalEvent(OnClearContainerSolutionMessage);
+
+ SubscribeLocalEvent(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
}
private void SubscribeUpdateUiState(Entity ent, ref T ev)
@@ -80,35 +88,38 @@ namespace Content.Server.Chemistry.EntitySystems
return null;
}
- private List GetInventory(Entity ent)
+ private List>> GetInventory(ReagentDispenserComponent reagentDispenser)
{
- var reagentDispenser = ent.Comp;
- var inventory = new List();
+ var inventory = new List>>();
- 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);
- if (HasComp(ent)
- && reagentDispenser.EmagPackPrototypeId is not null
- && _prototypeManager.TryIndex(reagentDispenser.EmagPackPrototypeId, out ReagentDispenserInventoryPrototype? emagPackPrototype))
- {
- inventory.AddRange(emagPackPrototype.Inventory.Select(x => new ReagentId(x, null)));
+ // Set label from manually-applied label, or metadata if unavailable
+ string reagentLabel;
+ if (TryComp(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));
+
+ inventory.Add(new KeyValuePair>(storageSlotId, new KeyValuePair(reagentLabel, storedAmount)));
}
return inventory;
}
- private void OnEmagged(Entity reagentDispenser, ref GotEmaggedEvent args)
- {
- // adding component manually to have correct state
- EntityManager.AddComponent(reagentDispenser);
- UpdateUiState(reagentDispenser);
- args.Handled = true;
- }
-
private void OnSetDispenseAmountMessage(Entity reagentDispenser, ref ReagentDispenserSetDispenseAmountMessage message)
{
reagentDispenser.Comp.DispenseAmount = message.ReagentDispenserDispenseAmount;
@@ -119,18 +130,23 @@ namespace Content.Server.Chemistry.EntitySystems
private void OnDispenseReagentMessage(Entity 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));
}
+
+ ///
+ /// Automatically generate storage slots for all NumSlots, and fill them with their initial chemicals.
+ /// The actual spawning of entities happens in ItemSlotsSystem's MapInit.
+ ///
+ private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
+ {
+ // Get list of pre-loaded containers
+ List preLoad = new List();
+ 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);
+ }
}
}
diff --git a/Content.Shared/Chemistry/Dispenser/ReagentDispenserInventoryPrototype.cs b/Content.Shared/Chemistry/Dispenser/ReagentDispenserInventoryPrototype.cs
index c362535e4f..5cdc8aed80 100644
--- a/Content.Shared/Chemistry/Dispenser/ReagentDispenserInventoryPrototype.cs
+++ b/Content.Shared/Chemistry/Dispenser/ReagentDispenserInventoryPrototype.cs
@@ -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))]
+ [DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer))]
public List Inventory = new();
[ViewVariables, IdDataField]
diff --git a/Content.Shared/Chemistry/SharedReagentDispenser.cs b/Content.Shared/Chemistry/SharedReagentDispenser.cs
index 1ecb0993f5..be71bf8a77 100644
--- a/Content.Shared/Chemistry/SharedReagentDispenser.cs
+++ b/Content.Shared/Chemistry/SharedReagentDispenser.cs
@@ -8,7 +8,7 @@ namespace Content.Shared.Chemistry
///
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
///
/// A list of the reagents which this dispenser can dispense.
///
- public readonly List Inventory;
+ public readonly List>> Inventory;
public readonly ReagentDispenserDispenseAmount SelectedDispenseAmount;
- public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
+ public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List>> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
{
OutputContainer = outputContainer;
Inventory = inventory;
diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
index 9310617634..f02cdc48db 100644
--- a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
+++ b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs
@@ -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.
///
[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?
///
[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.
///
[DataField("swap")]
+ [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)]
public bool Swap = true;
public string? ID => ContainerSlot?.ID;
diff --git a/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml b/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml
index 1af7f4cef6..62b166ebc1 100644
--- a/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml
+++ b/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml
@@ -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
diff --git a/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml b/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml
index 6d9c729fc9..2b0fdfae6c 100644
--- a/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml
+++ b/Resources/Prototypes/Catalog/ReagentDispensers/chemical.yml
@@ -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
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml
index 06dc47ce43..7837ae4e76 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml
@@ -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
@@ -285,6 +289,8 @@
reagents:
- ReagentId: Rum
Quantity: 100
+ - type: Label
+ currentLabel: rum
- type: Sprite
sprite: Objects/Consumable/Drinks/rumbottle.rsi
@@ -332,6 +338,8 @@
reagents:
- ReagentId: Tequila
Quantity: 100
+ - type: Label
+ currentLabel: tequila
- type: Sprite
sprite: Objects/Consumable/Drinks/tequillabottle.rsi
@@ -347,6 +355,8 @@
reagents:
- ReagentId: Vermouth
Quantity: 100
+ - type: Label
+ currentLabel: vermouth
- type: Sprite
sprite: Objects/Consumable/Drinks/vermouthbottle.rsi
@@ -377,6 +387,8 @@
reagents:
- ReagentId: Whiskey
Quantity: 100
+ - type: Label
+ currentLabel: whiskey
- type: Sprite
sprite: Objects/Consumable/Drinks/whiskeybottle.rsi
@@ -392,6 +404,8 @@
reagents:
- ReagentId: Wine
Quantity: 100
+ - type: Label
+ currentLabel: wine
- type: Sprite
sprite: Objects/Consumable/Drinks/winebottle.rsi
@@ -417,6 +431,22 @@
- 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.
@@ -433,6 +463,22 @@
- 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
@@ -451,3 +497,258 @@
- 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
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index e175bbe728..672d6488e3 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -553,7 +553,7 @@
- type: Sprite
state: medical
- type: MachineBoard
- prototype: ChemDispenser
+ prototype: ChemDispenserEmpty
requirements:
Capacitor: 1
materialRequirements:
@@ -1145,7 +1145,7 @@
- type: Sprite
state: service
- type: MachineBoard
- prototype: BoozeDispenser
+ prototype: BoozeDispenserEmpty
materialRequirements:
Steel: 5
tagRequirements:
@@ -1178,7 +1178,7 @@
- type: Sprite
state: service
- type: MachineBoard
- prototype: soda_dispenser
+ prototype: SodaDispenserEmpty
materialRequirements:
Steel: 5
tagRequirements:
diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
index 139eecfe83..01f5e45c47 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml
@@ -47,6 +47,9 @@
price: 60
- type: Label
originalName: jug
+ - type: Tag
+ tags:
+ - ChemDispensable
- type: entity
parent: Jug
diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml
index e170ce8255..4ce88c1582 100644
--- a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml
+++ b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml
@@ -1,4 +1,4 @@
-- type: entity
+- type: entity
abstract: true
id: ReagentDispenserBase
parent: ConstructibleMachine
@@ -55,18 +55,20 @@
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
diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/booze.yml b/Resources/Prototypes/Entities/Structures/Dispensers/booze.yml
index 15e40f79c3..1583bc451d 100644
--- a/Resources/Prototypes/Entities/Structures/Dispensers/booze.yml
+++ b/Resources/Prototypes/Entities/Structures/Dispensers/booze.yml
@@ -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:
@@ -10,8 +11,10 @@
drawdepth: SmallObjects
state: booze
- type: ReagentDispenser
+ storageWhitelist:
+ tags:
+ - DrinkBottle
pack: BoozeDispenserInventory
- emagPack: BoozeDispenserEmagInventory
- type: Transform
noRot: false
- type: Machine
@@ -24,3 +27,14 @@
- Bartender
- type: StealTarget
stealGroup: BoozeDispenser
+
+- type: entity
+ id: BoozeDispenserEmpty
+ suffix: Empty
+ parent: BoozeDispenser
+ components:
+ - type: ReagentDispenser
+ storageWhitelist:
+ tags:
+ - DrinkBottle
+ pack: EmptyInventory
diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
index 916b2b748c..2d98b9ff35 100644
--- a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
+++ b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml
@@ -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
@@ -37,3 +40,12 @@
- Chemist
- type: StealTarget
stealGroup: ChemDispenser
+
+- type: entity
+ id: ChemDispenserEmpty
+ name: chemical dispenser
+ suffix: Empty
+ parent: ChemDispenser
+ components:
+ - type: ReagentDispenser
+ pack: EmptyInventory
diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/soda.yml b/Resources/Prototypes/Entities/Structures/Dispensers/soda.yml
index 4322d56947..323480506f 100644
--- a/Resources/Prototypes/Entities/Structures/Dispensers/soda.yml
+++ b/Resources/Prototypes/Entities/Structures/Dispensers/soda.yml
@@ -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:
@@ -10,8 +11,10 @@
drawdepth: SmallObjects
state: soda
- type: ReagentDispenser
+ storageWhitelist:
+ tags:
+ - DrinkBottle
pack: SodaDispenserInventory
- emagPack: SodaDispenserEmagInventory
- type: Transform
noRot: false
- type: Machine
@@ -22,3 +25,14 @@
- type: GuideHelp
guides:
- Bartender
+
+- type: entity
+ id: SodaDispenserEmpty
+ suffix: Empty
+ parent: soda_dispenser
+ components:
+ - type: ReagentDispenser
+ storageWhitelist:
+ tags:
+ - DrinkBottle
+ pack: EmptyInventory
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index 88186ef84b..8fb58db7c1 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -305,6 +305,9 @@
- type: Tag
id: Chicken
+- type: Tag
+ id: ChemDispensable # container that can go into the chem dispenser
+
- type: Tag
id: Cigarette
@@ -458,6 +461,9 @@
- type: Tag
id: DrinkSpaceGlue
+- type: Tag
+ id: DrinkBottle
+
- type: Tag
id: DroneUsable