Make reagent dispensers gridinv-based instead of pseudo-listinv (#34205)

This simplifies the code and makes the experience of examining contents
easier without the reagent dispenser UI, as well as adding the possibility
for dispensers to have items of heterogeneous sizes in them, which would
allow configuring reagent dispensers to accept smaller containers such
as beakers or vials in order to allow for more types of smaller quantities
of reagents, or other flexibilities brought by using a standard storage
component.
This commit is contained in:
pathetic meowmeow
2025-05-09 23:49:05 -04:00
committed by GitHub
parent 942b2b4dcb
commit 5a0e0524ca
13 changed files with 154 additions and 216 deletions

View File

@@ -1,11 +1,12 @@
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;
@@ -13,6 +14,8 @@ using Robust.Shared.Audio;
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
{
@@ -30,6 +33,7 @@ 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()
{
@@ -37,12 +41,13 @@ namespace Content.Server.Chemistry.EntitySystems
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)});
@@ -82,32 +87,31 @@ namespace Content.Server.Chemistry.EntitySystems
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;
@@ -122,8 +126,14 @@ namespace Content.Server.Chemistry.EntitySystems
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;
@@ -131,13 +141,13 @@ namespace Content.Server.Chemistry.EntitySystems
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);
}
@@ -146,6 +156,21 @@ namespace Content.Server.Chemistry.EntitySystems
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);
@@ -163,39 +188,11 @@ namespace Content.Server.Chemistry.EntitySystems
}
/// <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);
}
}
}