using Content.Shared.Chemistry;
+using Content.Shared.Storage;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
[GenerateTypedNameReferences]
public sealed partial class ReagentCardControl : Control
{
- public string StorageSlotId { get; }
- public Action<string>? OnPressed;
- public Action<string>? OnEjectButtonPressed;
+ public ItemStorageLocation StorageLocation { get; }
+ public Action<ItemStorageLocation>? OnPressed;
+ public Action<ItemStorageLocation>? OnEjectButtonPressed;
public ReagentCardControl(ReagentInventoryItem item)
{
RobustXamlLoader.Load(this);
- StorageSlotId = item.StorageSlotId;
+ StorageLocation = item.StorageLocation;
ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = item.ReagentColor };
ReagentNameLabel.Text = item.ReagentLabel;
FillLabel.Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", item.Quantity));;
if (item.Quantity == 0.0)
MainButton.Disabled = true;
- MainButton.OnPressed += args => OnPressed?.Invoke(StorageSlotId);
- EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageSlotId);
+ MainButton.OnPressed += args => OnPressed?.Invoke(StorageLocation);
+ EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageLocation);
}
}
_window.AmountGrid.OnButtonPressed += s => SendMessage(new ReagentDispenserSetDispenseAmountMessage(s));
- _window.OnDispenseReagentButtonPressed += (id) => SendMessage(new ReagentDispenserDispenseReagentMessage(id));
- _window.OnEjectJugButtonPressed += (id) => SendMessage(new ItemSlotButtonPressedEvent(id));
+ _window.OnDispenseReagentButtonPressed += (location) => SendMessage(new ReagentDispenserDispenseReagentMessage(location));
+ _window.OnEjectJugButtonPressed += (location) => SendMessage(new ReagentDispenserEjectContainerMessage(location));
}
/// <summary>
using Content.Client.UserInterface.Controls;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
+using Content.Shared.Storage;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
- public event Action<string>? OnDispenseReagentButtonPressed;
- public event Action<string>? OnEjectJugButtonPressed;
+ public event Action<ItemStorageLocation>? OnDispenseReagentButtonPressed;
+ public event Action<ItemStorageLocation>? OnEjectJugButtonPressed;
/// <summary>
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
using Content.Shared.Containers.ItemSlots;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry;
-using Content.Shared.Chemistry.Dispenser;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
[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!;
-
- /// <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;
-
[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");
+using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry;
-using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.FixedPoint;
using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Storage.EntitySystems;
using JetBrains.Annotations;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Content.Shared.Labels.Components;
+using Content.Shared.Storage;
+using Content.Server.Hands.Systems;
namespace Content.Server.Chemistry.EntitySystems
{
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
+ [Dependency] private readonly HandsSystem _handsSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, SolutionContainerChangedEvent>(SubscribeUpdateUiState);
- SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
- SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
+ SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
+ SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
+ SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserEjectContainerMessage>(OnEjectReagentMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
private List<ReagentInventoryItem> GetInventory(Entity<ReagentDispenserComponent> reagentDispenser)
{
+ if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
+ {
+ return [];
+ }
+
var inventory = new List<ReagentInventoryItem>();
- for (var i = 0; i < reagentDispenser.Comp.NumSlots; i++)
+ foreach (var (storedContainer, storageLocation) in storage.StoredItems)
{
- 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;
+ reagentLabel = Name(storedContainer);
// Get volume remaining and color of solution
FixedPoint2 quantity = 0f;
var reagentColor = Color.White;
- if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol))
+ if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer, out _, out var sol))
{
quantity = sol.Volume;
reagentColor = sol.GetColor(_prototypeManager);
}
- inventory.Add(new ReagentInventoryItem(storageSlotId, reagentLabel, quantity, reagentColor));
+ inventory.Add(new ReagentInventoryItem(storageLocation, reagentLabel, quantity, reagentColor));
}
return inventory;
private void OnDispenseReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserDispenseReagentMessage message)
{
+ if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
+ {
+ return;
+ }
+
// Ensure that the reagent is something this reagent dispenser can dispense.
- var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, message.SlotId);
+ var storageLocation = message.StorageLocation;
+ var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key;
if (storedContainer == null)
return;
if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
return;
- if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out var src, out _) &&
+ if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer, out var src, out _) &&
_solutionContainerSystem.TryGetRefillableSolution(outputContainer.Value, out var dst, out _))
{
// force open container, if applicable, to avoid confusing people on why it doesn't dispense
- _openable.SetOpen(storedContainer.Value, true);
+ _openable.SetOpen(storedContainer, true);
_solutionTransferSystem.Transfer(reagentDispenser,
- storedContainer.Value, src.Value,
+ storedContainer, src.Value,
outputContainer.Value, dst.Value,
(int)reagentDispenser.Comp.DispenseAmount);
}
ClickSound(reagentDispenser);
}
+ private void OnEjectReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserEjectContainerMessage message)
+ {
+ if (!TryComp<StorageComponent>(reagentDispenser.Owner, out var storage))
+ {
+ return;
+ }
+
+ var storageLocation = message.StorageLocation;
+ var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key;
+ if (storedContainer == null)
+ return;
+
+ _handsSystem.TryPickupAnyHand(message.Actor, storedContainer);
+ }
+
private void OnClearContainerSolutionMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserClearContainerSolutionMessage message)
{
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
}
/// <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.
+ /// Initializes the beaker slot
/// </summary>
- private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
+ private void OnMapInit(Entity<ReagentDispenserComponent> ent, ref 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, SharedReagentDispenser.OutputSlotName, component.BeakerSlot);
+ _itemSlotsSystem.AddItemSlot(ent.Owner, SharedReagentDispenser.OutputSlotName, ent.Comp.BeakerSlot);
}
}
}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Chemistry.Dispenser
-{
- /// <summary>
- /// Is simply a list of reagents defined in yaml. This can then be set as a
- /// <see cref="SharedReagentDispenserComponent"/>s <c>pack</c> value (also in yaml),
- /// to define which reagents it's able to dispense. Based off of how vending
- /// machines define their inventory.
- /// </summary>
- [Serializable, NetSerializable, Prototype]
- public sealed partial class ReagentDispenserInventoryPrototype : IPrototype
- {
- [DataField("inventory", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
- public List<string> Inventory = new();
-
- [ViewVariables, IdDataField]
- public string ID { get; private set; } = default!;
- }
-}
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
+using Content.Shared.Storage;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
[Serializable, NetSerializable]
public sealed class ReagentDispenserDispenseReagentMessage : BoundUserInterfaceMessage
{
- public readonly string SlotId;
+ public readonly ItemStorageLocation StorageLocation;
- public ReagentDispenserDispenseReagentMessage(string slotId)
+ public ReagentDispenserDispenseReagentMessage(ItemStorageLocation storageLocation)
{
- SlotId = slotId;
+ StorageLocation = storageLocation;
+ }
+ }
+
+ /// <summary>
+ /// Message sent by the user interface to ask the reagent dispenser to eject a container
+ /// </summary>
+ [Serializable, NetSerializable]
+ public sealed class ReagentDispenserEjectContainerMessage : BoundUserInterfaceMessage
+ {
+ public readonly ItemStorageLocation StorageLocation;
+
+ public ReagentDispenserEjectContainerMessage(ItemStorageLocation storageLocation)
+ {
+ StorageLocation = storageLocation;
}
}
}
[Serializable, NetSerializable]
- public sealed class ReagentInventoryItem(string storageSlotId, string reagentLabel, FixedPoint2 quantity, Color reagentColor)
+ public sealed class ReagentInventoryItem(ItemStorageLocation storageLocation, string reagentLabel, FixedPoint2 quantity, Color reagentColor)
{
- public string StorageSlotId = storageSlotId;
+ public ItemStorageLocation StorageLocation = storageLocation;
public string ReagentLabel = reagentLabel;
public FixedPoint2 Quantity = quantity;
public Color ReagentColor = reagentColor;
+++ /dev/null
-- type: reagentDispenserInventory
- id: SodaDispenserInventory
- inventory:
- - DrinkCoconutWaterJug
- - DrinkCoffeeJug
- - DrinkColaBottleFull
- - DrinkCreamCartonXL
- - DrinkDrGibbJug
- - DrinkEnergyDrinkJug
- - DrinkGreenTeaJug
- - DrinkIceJug
- - DrinkJuiceLimeCartonXL
- - DrinkJuiceOrangeCartonXL
- - DrinkLemonLimeJug
- - DrinkRootBeerJug
- - DrinkSodaWaterBottleFull
- - DrinkSpaceMountainWindBottleFull
- - DrinkSpaceUpBottleFull
- - DrinkSugarJug
- - DrinkTeaJug
- - DrinkTonicWaterBottleFull
- - DrinkWaterMelonJuiceJug
-
-- type: reagentDispenserInventory
- id: BoozeDispenserInventory
- inventory:
- - DrinkAleBottleFullGrowler
- - DrinkBeerGrowler
- - DrinkCoffeeLiqueurBottleFull
- - DrinkCognacBottleFull
- - DrinkGinBottleFull
- - DrinkMeadJug
- - DrinkRumBottleFull
- - DrinkTequilaBottleFull
- - DrinkVermouthBottleFull
- - DrinkVodkaBottleFull
- - DrinkWhiskeyBottleFull
- - DrinkWineBottleFull
+++ /dev/null
-- type: reagentDispenserInventory
- id: ChemDispenserStandardInventory
- inventory:
- - JugAluminium
- - JugCarbon
- - JugChlorine
- - JugCopper
- - JugEthanol
- - JugFluorine
- - JugSugar
- - JugHydrogen
- - JugIodine
- - JugIron
- - JugLithium
- - JugMercury
- - JugNitrogen
- - JugOxygen
- - JugPhosphorus
- - JugPotassium
- - JugRadium
- - JugSilicon
- - JugSodium
- - JugSulfur
-
-- type: reagentDispenserInventory
- id: EmptyInventory
interfaces:
enum.ReagentDispenserUiKey.Key:
type: ReagentDispenserBoundUserInterface
+ enum.StorageUiKey.Key:
+ type: StorageBoundUserInterface
- type: Anchorable
- type: Pullable
- type: Damageable
- !type:PlaySoundBehavior
sound:
collection: MetalGlassBreak
+ - type: Storage
+ maxItemSize: Normal
+ grid:
+ - 0,0,19,5
- type: ReagentDispenser
- storageWhitelist:
- tags:
- - Bottle
beakerSlot:
whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message
whitelist:
machine_board: !type:Container
machine_parts: !type:Container
beakerSlot: !type:ContainerSlot
+ storagebase: !type:Container
- type: StaticPrice
price: 1000
- type: WiresPanel
sprite: Structures/smalldispensers.rsi
drawdepth: SmallObjects
state: booze
- - type: ReagentDispenser
- storageWhitelist:
+ - type: Storage
+ whitelist:
tags:
- DrinkBottle
- pack: BoozeDispenserInventory
+ - type: StorageFill
+ contents:
+ - id: DrinkAleBottleFullGrowler
+ - id: DrinkBeerGrowler
+ - id: DrinkCoffeeLiqueurBottleFull
+ - id: DrinkCognacBottleFull
+ - id: DrinkGinBottleFull
+ - id: DrinkMeadJug
+ - id: DrinkRumBottleFull
+ - id: DrinkTequilaBottleFull
+ - id: DrinkVermouthBottleFull
+ - id: DrinkVodkaBottleFull
+ - id: DrinkWhiskeyBottleFull
+ - id: DrinkWineBottleFull
- type: Transform
noRot: false
- type: Machine
suffix: Empty
parent: BoozeDispenser
components:
- - type: ReagentDispenser
- storageWhitelist:
+ - type: Storage
+ whitelist:
tags:
- DrinkBottle
- pack: EmptyInventory
- type: entity
- id: ChemDispenser
+ id: ChemDispenserEmpty
name: chemical dispenser
- suffix: Filled
+ suffix: Empty
parent: ReagentDispenserBase
description: An industrial grade chemical dispenser.
components:
sprite: Structures/dispensers.rsi
state: industrial-working
snapCardinals: true
- - type: ReagentDispenser
- storageWhitelist:
+ - type: Storage
+ whitelist:
tags:
- ChemDispensable
- pack: ChemDispenserStandardInventory
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: Destructible
- MachineLayer
- type: entity
- id: ChemDispenserEmpty
+ id: ChemDispenser
name: chemical dispenser
- suffix: Empty
- parent: ChemDispenser
+ suffix: Filled
+ parent: ChemDispenserEmpty
components:
- type: ReagentDispenser
- pack: EmptyInventory
+ - type: StorageFill
+ contents:
+ - id: JugAluminium
+ - id: JugCarbon
+ - id: JugChlorine
+ - id: JugCopper
+ - id: JugEthanol
+ - id: JugFluorine
+ - id: JugSugar
+ - id: JugHydrogen
+ - id: JugIodine
+ - id: JugIron
+ - id: JugLithium
+ - id: JugMercury
+ - id: JugNitrogen
+ - id: JugOxygen
+ - id: JugPhosphorus
+ - id: JugPotassium
+ - id: JugRadium
+ - id: JugSilicon
+ - id: JugSodium
+ - id: JugSulfur
sprite: Structures/smalldispensers.rsi
drawdepth: SmallObjects
state: soda
- - type: ReagentDispenser
- storageWhitelist:
+ - type: Storage
+ whitelist:
tags:
- DrinkBottle
- pack: SodaDispenserInventory
+ - type: StorageFill
+ contents:
+ - id: DrinkCoconutWaterJug
+ - id: DrinkCoffeeJug
+ - id: DrinkColaBottleFull
+ - id: DrinkCreamCartonXL
+ - id: DrinkDrGibbJug
+ - id: DrinkEnergyDrinkJug
+ - id: DrinkGreenTeaJug
+ - id: DrinkIceJug
+ - id: DrinkJuiceLimeCartonXL
+ - id: DrinkJuiceOrangeCartonXL
+ - id: DrinkLemonLimeJug
+ - id: DrinkRootBeerJug
+ - id: DrinkSodaWaterBottleFull
+ - id: DrinkSpaceMountainWindBottleFull
+ - id: DrinkSpaceUpBottleFull
+ - id: DrinkSugarJug
+ - id: DrinkTeaJug
+ - id: DrinkTonicWaterBottleFull
+ - id: DrinkWaterMelonJuiceJug
- type: Transform
noRot: false
- type: Machine
parent: SodaDispenser
id: SodaDispenserEmpty
suffix: Empty
- components:
- - type: ReagentDispenser
- storageWhitelist:
- tags:
- - DrinkBottle
- pack: EmptyInventory