ReagentDispenser ECS (#11418)

This commit is contained in:
0x6273
2022-10-04 02:57:32 +02:00
committed by GitHub
parent f89b4f0a1d
commit 0c24f8b69b
17 changed files with 351 additions and 640 deletions

View File

@@ -1,12 +0,0 @@
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
namespace Content.Client.Chemistry.EntitySystems
{
[UsedImplicitly]
public sealed class ReagentDispenserSystem : SharedReagentDispenserSystem
{
// hello there.
// General Kenobi
}
}

View File

@@ -12,6 +12,7 @@ namespace Content.Client.Chemistry.UI
[UsedImplicitly] [UsedImplicitly]
public sealed class ChemMasterBoundUserInterface : BoundUserInterface public sealed class ChemMasterBoundUserInterface : BoundUserInterface
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
private ChemMasterWindow? _window; private ChemMasterWindow? _window;
public ChemMasterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) public ChemMasterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
@@ -30,7 +31,7 @@ namespace Content.Client.Chemistry.UI
// Setup window layout/elements // Setup window layout/elements
_window = new ChemMasterWindow _window = new ChemMasterWindow
{ {
Title = Loc.GetString("chem-master-bound-user-interface-title"), Title = _entityManager.GetComponent<MetaDataComponent>(Owner.Owner).EntityName,
}; };
_window.OpenCentered(); _window.OpenCentered();

View File

@@ -98,7 +98,6 @@ namespace Content.Client.Chemistry.UI
public void UpdateState(BoundUserInterfaceState state) public void UpdateState(BoundUserInterfaceState state)
{ {
var castState = (ChemMasterBoundUserInterfaceState) state; var castState = (ChemMasterBoundUserInterfaceState) state;
Title = castState.DispenserName;
if (castState.UpdateLabel) if (castState.UpdateLabel)
LabelLine = GenerateLabel(castState); LabelLine = GenerateLabel(castState);
UpdatePanelInfo(castState); UpdatePanelInfo(castState);

View File

@@ -1,10 +1,7 @@
using System.Linq; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using static Content.Shared.Chemistry.Dispenser.SharedReagentDispenserComponent;
namespace Content.Client.Chemistry.UI namespace Content.Client.Chemistry.UI
{ {
@@ -14,6 +11,7 @@ namespace Content.Client.Chemistry.UI
[UsedImplicitly] [UsedImplicitly]
public sealed class ReagentDispenserBoundUserInterface : BoundUserInterface public sealed class ReagentDispenserBoundUserInterface : BoundUserInterface
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
private ReagentDispenserWindow? _window; private ReagentDispenserWindow? _window;
private ReagentDispenserBoundUserInterfaceState? _lastState; private ReagentDispenserBoundUserInterfaceState? _lastState;
@@ -31,30 +29,44 @@ namespace Content.Client.Chemistry.UI
base.Open(); base.Open();
// Setup window layout/elements // Setup window layout/elements
_window = new(); _window = new()
{
Title = _entityManager.GetComponent<MetaDataComponent>(Owner.Owner).EntityName,
};
_window.OpenCentered(); _window.OpenCentered();
_window.OnClose += Close; _window.OnClose += Close;
// Setup static button actions. // Setup static button actions.
_window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(BeakerSlotId)); _window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName));
_window.ClearButton.OnPressed += _ => ButtonPressed(UiButton.Clear); _window.ClearButton.OnPressed += _ => SendMessage(new ReagentDispenserClearContainerSolutionMessage());
_window.DispenseButton1.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount1); _window.DispenseButton1.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U1));
_window.DispenseButton5.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount5); _window.DispenseButton5.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U5));
_window.DispenseButton10.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount10); _window.DispenseButton10.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U10));
_window.DispenseButton15.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount15); _window.DispenseButton15.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U15));
_window.DispenseButton20.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount20); _window.DispenseButton20.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U20));
_window.DispenseButton25.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount25); _window.DispenseButton25.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U25));
_window.DispenseButton30.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount30); _window.DispenseButton30.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U30));
_window.DispenseButton50.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount50); _window.DispenseButton50.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U50));
_window.DispenseButton100.OnPressed += _ => ButtonPressed(UiButton.SetDispenseAmount100); _window.DispenseButton100.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U100));
// Setup reagent button actions.
_window.OnDispenseReagentButtonPressed += (args, button) => SendMessage(new ReagentDispenserDispenseReagentMessage(button.ReagentId));
_window.OnDispenseReagentButtonMouseEntered += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState, button.ReagentId);
};
_window.OnDispenseReagentButtonMouseExited += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
} }
/// <summary> /// <summary>
/// Update the ui each time new state data is sent from the server. /// Update the UI each time new state data is sent from the server.
/// </summary> /// </summary>
/// <param name="state"> /// <param name="state">
/// Data of the <see cref="SharedReagentDispenserComponent"/> that this ui represents. /// Data of the <see cref="ReagentDispenserComponent"/> that this UI represents.
/// Sent from the server. /// Sent from the server.
/// </param> /// </param>
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)
@@ -64,50 +76,9 @@ namespace Content.Client.Chemistry.UI
var castState = (ReagentDispenserBoundUserInterfaceState)state; var castState = (ReagentDispenserBoundUserInterfaceState)state;
_lastState = castState; _lastState = castState;
UpdateReagentsList(castState.Inventory); //Update reagents list & reagent button actions
_window?.UpdateState(castState); //Update window state _window?.UpdateState(castState); //Update window state
} }
/// <summary>
/// Update the list of reagents that this dispenser can dispense on the UI.
/// </summary>
/// <param name="inventory">A list of the reagents which can be dispensed.</param>
private void UpdateReagentsList(List<ReagentDispenserInventoryEntry> inventory)
{
if (_window == null)
{
return;
}
_window.UpdateReagentsList(inventory);
for (var i = 0; i < _window.ChemicalList.Children.Count(); i++)
{
var button = (Button)_window.ChemicalList.Children.ElementAt(i);
var i1 = i;
button.OnPressed += _ => ButtonPressed(UiButton.Dispense, i1);
button.OnMouseEntered += _ =>
{
if (_lastState != null)
{
_window.UpdateContainerInfo(_lastState, inventory[i1].ID);
}
};
button.OnMouseExited += _ =>
{
if (_lastState != null)
{
_window.UpdateContainerInfo(_lastState);
}
};
}
}
private void ButtonPressed(UiButton button, int dispenseIndex = -1)
{
SendMessage(new UiButtonPressedMessage(button, dispenseIndex));
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
base.Dispose(disposing); base.Dispose(disposing);

View File

@@ -1,28 +1,27 @@
using System.Collections.Generic; using System.Linq;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Client.UserInterface; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Chemistry.Dispenser.SharedReagentDispenserComponent;
using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Chemistry.UI namespace Content.Client.Chemistry.UI
{ {
/// <summary> /// <summary>
/// Client-side UI used to control a <see cref="SharedReagentDispenserComponent"/> /// Client-side UI used to control a <see cref="ReagentDispenserComponent"/>.
/// </summary> /// </summary>
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ReagentDispenserWindow : DefaultWindow public sealed partial class ReagentDispenserWindow : DefaultWindow
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event Action<BaseButton.ButtonEventArgs, DispenseReagentButton>? OnDispenseReagentButtonPressed;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseExited;
/// <summary> /// <summary>
/// Create and initialize the dispenser UI client-side. Creates the basic layout, /// Create and initialize the dispenser UI client-side. Creates the basic layout,
@@ -47,26 +46,27 @@ namespace Content.Client.Chemistry.UI
/// <summary> /// <summary>
/// Update the button grid of reagents which can be dispensed. /// Update the button grid of reagents which can be dispensed.
/// <para>The actions for these buttons are set in <see cref="ReagentDispenserBoundUserInterface.UpdateReagentsList"/>.</para>
/// </summary> /// </summary>
/// <param name="inventory">Reagents which can be dispensed by this dispenser</param> /// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
public void UpdateReagentsList(List<ReagentDispenserInventoryEntry> inventory) public void UpdateReagentsList(List<string> inventory)
{ {
if (ChemicalList == null) return; if (ChemicalList == null) return;
if (inventory == null) return; if (inventory == null) return;
ChemicalList.Children.Clear(); ChemicalList.Children.Clear();
foreach (var entry in inventory) foreach (var entry in inventory
.OrderBy(r => {_prototypeManager.TryIndex(r, out ReagentPrototype? p); return p?.LocalizedName;}))
{ {
if (_prototypeManager.TryIndex(entry.ID, out ReagentPrototype? proto)) var localizedName = _prototypeManager.TryIndex(entry, out ReagentPrototype? p)
{ ? p.LocalizedName
ChemicalList.AddChild(new Button {Text = proto.LocalizedName}); : Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
}
else var button = new DispenseReagentButton(entry, localizedName);
{ button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
ChemicalList.AddChild(new Button {Text = Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text") }); button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
} button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
ChemicalList.AddChild(button);
} }
} }
@@ -77,50 +77,40 @@ namespace Content.Client.Chemistry.UI
public void UpdateState(BoundUserInterfaceState state) public void UpdateState(BoundUserInterfaceState state)
{ {
var castState = (ReagentDispenserBoundUserInterfaceState) state; var castState = (ReagentDispenserBoundUserInterfaceState) state;
Title = castState.DispenserName;
UpdateContainerInfo(castState); UpdateContainerInfo(castState);
UpdateReagentsList(castState.Inventory);
// Disable all buttons if not powered
if (Contents.Children != null)
{
ButtonHelpers.SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
}
// Disable the Clear & Eject button if no beaker // Disable the Clear & Eject button if no beaker
if (!castState.HasBeaker) ClearButton.Disabled = castState.OutputContainer is null;
{ EjectButton.Disabled = castState.OutputContainer is null;
ClearButton.Disabled = true;
EjectButton.Disabled = true;
}
switch (castState.SelectedDispenseAmount.Int()) switch (castState.SelectedDispenseAmount)
{ {
case 1: case ReagentDispenserDispenseAmount.U1:
DispenseButton1.Pressed = true; DispenseButton1.Pressed = true;
break; break;
case 5: case ReagentDispenserDispenseAmount.U5:
DispenseButton5.Pressed = true; DispenseButton5.Pressed = true;
break; break;
case 10: case ReagentDispenserDispenseAmount.U10:
DispenseButton10.Pressed = true; DispenseButton10.Pressed = true;
break; break;
case 15: case ReagentDispenserDispenseAmount.U15:
DispenseButton15.Pressed = true; DispenseButton15.Pressed = true;
break; break;
case 20: case ReagentDispenserDispenseAmount.U20:
DispenseButton20.Pressed = true; DispenseButton20.Pressed = true;
break; break;
case 25: case ReagentDispenserDispenseAmount.U25:
DispenseButton25.Pressed = true; DispenseButton25.Pressed = true;
break; break;
case 30: case ReagentDispenserDispenseAmount.U30:
DispenseButton30.Pressed = true; DispenseButton30.Pressed = true;
break; break;
case 50: case ReagentDispenserDispenseAmount.U50:
DispenseButton50.Pressed = true; DispenseButton50.Pressed = true;
break; break;
case 100: case ReagentDispenserDispenseAmount.U100:
DispenseButton100.Pressed = true; DispenseButton100.Pressed = true;
break; break;
} }
@@ -131,12 +121,13 @@ namespace Content.Client.Chemistry.UI
/// <para>Also highlights a reagent if it's dispense button is being mouse hovered.</para> /// <para>Also highlights a reagent if it's dispense button is being mouse hovered.</para>
/// </summary> /// </summary>
/// <param name="state">State data for the dispenser.</param> /// <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.</param> /// <param name="highlightedReagentId">Prototype ID of the reagent whose dispense button is currently being mouse hovered,
public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, string highlightedReagentId = "") /// or null if no button is being hovered.</param>
public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state, string? highlightedReagentId = null)
{ {
ContainerInfo.Children.Clear(); ContainerInfo.Children.Clear();
if (!state.HasBeaker) if (state.OutputContainer is null)
{ {
ContainerInfo.Children.Add(new Label {Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") }); ContainerInfo.Children.Add(new Label {Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
return; return;
@@ -147,67 +138,55 @@ namespace Content.Client.Chemistry.UI
Orientation = LayoutOrientation.Horizontal, Orientation = LayoutOrientation.Horizontal,
Children = Children =
{ {
new Label {Text = $"{state.ContainerName}: "}, new Label {Text = $"{state.OutputContainer.DisplayName}: "},
new Label new Label
{ {
Text = $"{state.BeakerCurrentVolume}/{state.BeakerMaxVolume}", Text = $"{state.OutputContainer.CurrentVolume}/{state.OutputContainer.MaxVolume}",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
} }
} }
}); });
if (state.ContainerReagents == null) foreach (var reagent in state.OutputContainer.Contents)
{ {
return; // Try get to the prototype for the given reagent. This gives us its name.
} var localizedName = _prototypeManager.TryIndex(reagent.Id, out ReagentPrototype? p)
? p.LocalizedName
: Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
foreach (var reagent in state.ContainerReagents) var nameLabel = new Label {Text = $"{localizedName}: "};
var quantityLabel = new Label
{ {
var name = Loc.GetString("reagent-dispenser-window-unknown-reagent-text"); Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", reagent.Quantity)),
//Try to the prototype for the given reagent. This gives us it's name. StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype? proto)) };
{
name = proto.LocalizedName;
}
// Check if the reagent is being moused over. If so, color it green. // Check if the reagent is being moused over. If so, color it green.
if (proto != null && proto.ID == highlightedReagentId) if (reagent.Id == highlightedReagentId) {
{ nameLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
quantityLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
}
ContainerInfo.Children.Add(new BoxContainer ContainerInfo.Children.Add(new BoxContainer
{ {
Orientation = LayoutOrientation.Horizontal, Orientation = LayoutOrientation.Horizontal,
Children = Children =
{ {
new Label nameLabel,
{ quantityLabel,
Text = $"{name}: ",
StyleClasses = {StyleNano.StyleClassPowerStateGood}
},
new Label
{
Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", reagent.Quantity)),
StyleClasses = {StyleNano.StyleClassPowerStateGood}
}
}
});
}
else //Otherwise, color it the normal colors.
{
ContainerInfo.Children.Add(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = $"{name}: "},
new Label
{
Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", reagent.Quantity)),
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}
} }
}); });
} }
} }
} }
public sealed class DispenseReagentButton : Button {
public string ReagentId { get; }
public DispenseReagentButton(string reagentId, string text)
{
ReagentId = reagentId;
Text = text;
}
} }
} }

View File

@@ -103,7 +103,6 @@ namespace Content.Client.Entry
// Do not add to these, they are legacy. // Do not add to these, they are legacy.
_componentFactory.RegisterClass<SharedSpawnPointComponent>(); _componentFactory.RegisterClass<SharedSpawnPointComponent>();
_componentFactory.RegisterClass<SharedReagentDispenserComponent>();
_componentFactory.RegisterClass<SharedGravityGeneratorComponent>(); _componentFactory.RegisterClass<SharedGravityGeneratorComponent>();
_componentFactory.RegisterClass<SharedAMEControllerComponent>(); _componentFactory.RegisterClass<SharedAMEControllerComponent>();
// Do not add to the above, they are legacy // Do not add to the above, they are legacy

View File

@@ -1,281 +1,34 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.Power.Components; using Content.Shared.Chemistry;
using Content.Server.UserInterface;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Dispenser; using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.Components namespace Content.Server.Chemistry.Components
{ {
/// <summary> /// <summary>
/// Contains all the server-side logic for reagent dispensers. See also <see cref="SharedReagentDispenserComponent"/>. /// A machine that dispenses reagents into a solution container.
/// This includes initializing the component based on prototype data, and sending and receiving messages from the client.
/// Messages sent to the client are used to update update the user interface for a component instance.
/// Messages sent from the client are used to handle ui button presses.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(SharedReagentDispenserComponent))] [Access(typeof(ReagentDispenserSystem))]
public sealed class ReagentDispenserComponent : SharedReagentDispenserComponent public sealed class ReagentDispenserComponent : Component
{ {
private static ReagentInventoryComparer _comparer = new();
public static string SolutionName = "reagent";
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
[Dependency] private readonly IEntityManager _entities = default!; [ViewVariables(VVAccess.ReadWrite)]
[Dependency] private readonly IAdminLogManager _adminLogger = default!; public string? PackPrototypeId = default!;
[ViewVariables] [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))] private string _packPrototypeId = ""; [DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string? EmagPackPrototypeId = default!;
[ViewVariables] [DataField("emagPack", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentDispenserInventoryPrototype>))] public string EmagPackPrototypeId = ""; [ViewVariables(VVAccess.ReadWrite)]
public bool IsEmagged = false;
public bool AlreadyEmagged = false; [DataField("clickSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
[DataField("clickSound")] [ViewVariables(VVAccess.ReadWrite)]
private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); public ReagentDispenserDispenseAmount DispenseAmount = ReagentDispenserDispenseAmount.U10;
[ViewVariables] private FixedPoint2 _dispenseAmount = FixedPoint2.New(10);
[UsedImplicitly]
[ViewVariables]
private Solution? Solution
{
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution);
return solution;
}
}
[ViewVariables]
private bool Powered => !_entities.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key);
/// <summary>
/// Called once per instance of this component. Gets references to any other components needed
/// by this component and initializes it's UI and other data.
/// </summary>
protected override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
InitializeFromPrototype();
}
/// <summary>
/// Checks to see if the <c>pack</c> defined in this components yaml prototype
/// exists. If so, it fills the reagent inventory list.
/// </summary>
private void InitializeFromPrototype()
{
if (string.IsNullOrEmpty(_packPrototypeId)) return;
if (!_prototypeManager.TryIndex(_packPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
{
return;
}
foreach (var entry in packPrototype.Inventory)
{
Inventory.Add(new ReagentDispenserInventoryEntry(entry));
}
Inventory.Sort(_comparer);
}
public void AddFromPrototype(string pack)
{
if (string.IsNullOrEmpty(pack)) return;
if (!_prototypeManager.TryIndex(pack, out ReagentDispenserInventoryPrototype? packPrototype))
{
return;
}
foreach (var entry in packPrototype.Inventory)
{
Inventory.Add(new ReagentDispenserInventoryEntry(entry));
}
Inventory.Sort(_comparer);
}
public void OnPowerChanged()
{
UpdateUserInterface();
}
/// <summary>
/// Handles ui messages from the client. For things such as button presses
/// which interact with the world and require server action.
/// </summary>
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
if (obj.Message is not UiButtonPressedMessage msg )
return;
if (!PlayerCanUseDispenser(obj.Session.AttachedEntity, true))
return;
switch (msg.Button)
{
case UiButton.Clear:
TryClear();
break;
case UiButton.SetDispenseAmount1:
_dispenseAmount = FixedPoint2.New(1);
break;
case UiButton.SetDispenseAmount5:
_dispenseAmount = FixedPoint2.New(5);
break;
case UiButton.SetDispenseAmount10:
_dispenseAmount = FixedPoint2.New(10);
break;
case UiButton.SetDispenseAmount15:
_dispenseAmount = FixedPoint2.New(15);
break;
case UiButton.SetDispenseAmount20:
_dispenseAmount = FixedPoint2.New(20);
break;
case UiButton.SetDispenseAmount25:
_dispenseAmount = FixedPoint2.New(25);
break;
case UiButton.SetDispenseAmount30:
_dispenseAmount = FixedPoint2.New(30);
break;
case UiButton.SetDispenseAmount50:
_dispenseAmount = FixedPoint2.New(50);
break;
case UiButton.SetDispenseAmount100:
_dispenseAmount = FixedPoint2.New(100);
break;
case UiButton.Dispense:
if (BeakerSlot.HasItem)
{
TryDispense(msg.DispenseIndex);
// Ew
if (BeakerSlot.Item != null)
_adminLogger.Add(LogType.ChemicalReaction, LogImpact.Medium,
$"{_entities.ToPrettyString(obj.Session.AttachedEntity.Value):player} dispensed {_dispenseAmount}u of {Inventory[msg.DispenseIndex].ID} into {_entities.ToPrettyString(BeakerSlot.Item.Value):entity}");
}
break;
default:
throw new ArgumentOutOfRangeException();
}
ClickSound();
}
/// <summary>
/// Checks whether the player entity is able to use the chem dispenser.
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the dispenser, and false if it cannot.</returns>
private bool PlayerCanUseDispenser(EntityUid? playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
return false;
//Check if device is powered
if (needsPower && !Powered)
return false;
return true;
}
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedReagentDispenserComponent.ReagentDispenserBoundUserInterfaceState"/></returns>
private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState()
{
if (BeakerSlot.Item is not {Valid: true} beaker ||
!_entities.TryGetComponent(beaker, out FitsInDispenserComponent? fits) ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, fits.Solution, out var solution))
{
return new ReagentDispenserBoundUserInterfaceState(Powered, false, FixedPoint2.New(0),
FixedPoint2.New(0),
string.Empty, Inventory, _entities.GetComponent<MetaDataComponent>(Owner).EntityName, null, _dispenseAmount);
}
return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume,
solution.MaxVolume,
_entities.GetComponent<MetaDataComponent>(beaker).EntityName, Inventory, _entities.GetComponent<MetaDataComponent>(Owner).EntityName, solution.Contents.ToList(), _dispenseAmount);
}
public void UpdateUserInterface()
{
if (!Initialized) return;
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
/// <summary>
/// If this component contains an entity with a <see cref="SolutionHolder"/>, remove all of it's reagents / solutions.
/// </summary>
private void TryClear()
{
if (BeakerSlot.Item is not {Valid: true} beaker ||
!_entities.TryGetComponent(beaker, out FitsInDispenserComponent? fits) ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, fits.Solution, out var solution))
return;
EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(beaker, solution);
UpdateUserInterface();
}
/// <summary>
/// If this component contains an entity with a <see cref="SolutionHolder"/>, attempt to dispense the specified reagent to it.
/// </summary>
/// <param name="dispenseIndex">The index of the reagent in <c>Inventory</c>.</param>
private void TryDispense(int dispenseIndex)
{
if (BeakerSlot.Item is not {Valid: true} beaker ||
!_entities.TryGetComponent(beaker, out FitsInDispenserComponent? fits) ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, fits.Solution, out var solution)) return;
EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(beaker, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _);
UpdateUserInterface();
}
private void ClickSound()
{
SoundSystem.Play(_clickSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(-2f));
}
private sealed class ReagentInventoryComparer : Comparer<ReagentDispenserInventoryEntry>
{
public override int Compare(ReagentDispenserInventoryEntry x, ReagentDispenserInventoryEntry y)
{
return string.Compare(x.ID, y.ID, StringComparison.InvariantCultureIgnoreCase);
}
}
} }
} }

View File

@@ -59,18 +59,15 @@ namespace Content.Server.Chemistry.EntitySystems
{ {
if (!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out var bufferSolution)) if (!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out var bufferSolution))
return; return;
var inputContainer = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.InputSlotName); var inputContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.InputSlotName);
var outputContainer = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.OutputSlotName); var outputContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.OutputSlotName);
var dispenserName = Name(chemMaster.Owner);
var bufferReagents = bufferSolution.Contents; var bufferReagents = bufferSolution.Contents;
var bufferCurrentVolume = bufferSolution.CurrentVolume; var bufferCurrentVolume = bufferSolution.CurrentVolume;
var state = new ChemMasterBoundUserInterfaceState( var state = new ChemMasterBoundUserInterfaceState(
chemMaster.Mode, dispenserName, chemMaster.Mode, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer),
BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer), bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel);
bufferReagents, bufferCurrentVolume,
chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel);
_userInterfaceSystem.TrySetUiState(chemMaster.Owner, ChemMasterUiKey.Key, state); _userInterfaceSystem.TrySetUiState(chemMaster.Owner, ChemMasterUiKey.Key, state);
} }
@@ -121,7 +118,7 @@ namespace Content.Server.Chemistry.EntitySystems
private void TransferReagents(ChemMasterComponent chemMaster, string reagentId, FixedPoint2 amount, bool fromBuffer) private void TransferReagents(ChemMasterComponent chemMaster, string reagentId, FixedPoint2 amount, bool fromBuffer)
{ {
var container = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.InputSlotName); var container = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.InputSlotName);
if (container is null || if (container is null ||
!_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution) || !_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution) ||
!_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out var bufferSolution)) !_solutionContainerSystem.TryGetSolution(chemMaster.Owner, SharedChemMaster.BufferSolutionName, out var bufferSolution))
@@ -157,7 +154,7 @@ namespace Content.Server.Chemistry.EntitySystems
} }
else else
{ {
var container = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.InputSlotName); var container = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.InputSlotName);
if (container is not null && if (container is not null &&
_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution)) _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution))
{ {
@@ -173,7 +170,7 @@ namespace Content.Server.Chemistry.EntitySystems
private void OnCreatePillsMessage(EntityUid uid, ChemMasterComponent chemMaster, ChemMasterCreatePillsMessage message) private void OnCreatePillsMessage(EntityUid uid, ChemMasterComponent chemMaster, ChemMasterCreatePillsMessage message)
{ {
var user = message.Session.AttachedEntity; var user = message.Session.AttachedEntity;
var maybeContainer = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.OutputSlotName); var maybeContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.OutputSlotName);
if (maybeContainer is not { Valid: true } container if (maybeContainer is not { Valid: true } container
|| !TryComp(container, out ServerStorageComponent? storage) || !TryComp(container, out ServerStorageComponent? storage)
|| storage.Storage is null) || storage.Storage is null)
@@ -218,7 +215,7 @@ namespace Content.Server.Chemistry.EntitySystems
EntityUid uid, ChemMasterComponent chemMaster, ChemMasterOutputToBottleMessage message) EntityUid uid, ChemMasterComponent chemMaster, ChemMasterOutputToBottleMessage message)
{ {
var user = message.Session.AttachedEntity; var user = message.Session.AttachedEntity;
var maybeContainer = _itemSlotsSystem.GetItem(chemMaster.Owner, SharedChemMaster.OutputSlotName); var maybeContainer = _itemSlotsSystem.GetItemOrNull(chemMaster.Owner, SharedChemMaster.OutputSlotName);
if (maybeContainer is not { Valid: true } container if (maybeContainer is not { Valid: true } container
|| !_solutionContainerSystem.TryGetSolution( || !_solutionContainerSystem.TryGetSolution(
container, SharedChemMaster.BottleSolutionName, out var solution)) container, SharedChemMaster.BottleSolutionName, out var solution))

View File

@@ -1,17 +0,0 @@
using Content.Server.Chemistry.Components;
using Content.Server.Power.Components;
namespace Content.Server.Chemistry.EntitySystems;
public sealed partial class ChemistrySystem
{
private void InitializeReagentDispenser()
{
SubscribeLocalEvent<ReagentDispenserComponent, PowerChangedEvent>(OnReagentDispenserPower);
}
private static void OnReagentDispenserPower(EntityUid uid, ReagentDispenserComponent component, PowerChangedEvent args)
{
component.OnPowerChanged();
}
}

View File

@@ -22,6 +22,5 @@ public sealed partial class ChemistrySystem : EntitySystem
// Why ChemMaster duplicates reagentdispenser nobody knows. // Why ChemMaster duplicates reagentdispenser nobody knows.
InitializeHypospray(); InitializeHypospray();
InitializeInjector(); InitializeInjector();
InitializeReagentDispenser();
} }
} }

View File

@@ -1,32 +1,145 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.EntitySystems namespace Content.Server.Chemistry.EntitySystems
{ {
/// <summary>
/// Contains all the server-side logic for reagent dispensers.
/// <seealso cref="ReagentDispenserComponent"/>
/// </summary>
[UsedImplicitly] [UsedImplicitly]
public sealed class ReagentDispenserSystem : SharedReagentDispenserSystem public sealed class ReagentDispenserSystem : EntitySystem
{ {
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = 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!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>((_, comp, _) => comp.UpdateUserInterface());
SubscribeLocalEvent<ReagentDispenserComponent, SolutionChangedEvent>((_, comp, _) => comp.UpdateUserInterface()); SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>((_, comp, _) => UpdateUiState(comp));
SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>((_, comp, _) => comp.UpdateUserInterface()); SubscribeLocalEvent<ReagentDispenserComponent, SolutionChangedEvent>((_, comp, _) => UpdateUiState(comp));
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>((_, comp, _) => comp.UpdateUserInterface()); SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>((_, comp, _) => UpdateUiState(comp));
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>((_, comp, _) => UpdateUiState(comp));
SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>((_, comp, _) => UpdateUiState(comp));
SubscribeLocalEvent<ReagentDispenserComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<ReagentDispenserComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
} }
private void OnEmagged(EntityUid uid, ReagentDispenserComponent comp, GotEmaggedEvent args) private void UpdateUiState(ReagentDispenserComponent reagentDispenser)
{ {
if (!comp.AlreadyEmagged) var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, SharedReagentDispenser.OutputSlotName);
var outputContainerInfo = BuildOutputContainerInfo(outputContainer);
var inventory = GetInventory(reagentDispenser);
var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, inventory, reagentDispenser.DispenseAmount);
_userInterfaceSystem.TrySetUiState(reagentDispenser.Owner, ReagentDispenserUiKey.Key, state);
}
private ContainerInfo? BuildOutputContainerInfo(EntityUid? container)
{ {
comp.AddFromPrototype(comp.EmagPackPrototypeId); if (container is not { Valid: true })
comp.AlreadyEmagged = true; return null;
if (_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var solution))
{
var reagents = solution.Contents.Select(reagent => (reagent.ReagentId, reagent.Quantity)).ToList();
return new ContainerInfo(Name(container.Value), true, solution.CurrentVolume, solution.MaxVolume, reagents);
}
return null;
}
private List<string> GetInventory(ReagentDispenserComponent reagentDispenser)
{
var inventory = new List<string>();
if (reagentDispenser.PackPrototypeId is not null
&& _prototypeManager.TryIndex(reagentDispenser.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
{
inventory.AddRange(packPrototype.Inventory);
}
if (reagentDispenser.IsEmagged
&& reagentDispenser.EmagPackPrototypeId is not null
&& _prototypeManager.TryIndex(reagentDispenser.EmagPackPrototypeId, out ReagentDispenserInventoryPrototype? emagPackPrototype))
{
inventory.AddRange(emagPackPrototype.Inventory);
}
return inventory;
}
private void OnEmagged(EntityUid uid, ReagentDispenserComponent reagentDispenser, GotEmaggedEvent args)
{
if (!reagentDispenser.IsEmagged)
{
reagentDispenser.IsEmagged = true;
args.Handled = true; args.Handled = true;
} UpdateUiState(reagentDispenser);
}
}
private void OnSetDispenseAmountMessage(EntityUid uid, ReagentDispenserComponent reagentDispenser, ReagentDispenserSetDispenseAmountMessage message)
{
reagentDispenser.DispenseAmount = message.ReagentDispenserDispenseAmount;
UpdateUiState(reagentDispenser);
ClickSound(reagentDispenser);
}
private void OnDispenseReagentMessage(EntityUid uid, ReagentDispenserComponent reagentDispenser, ReagentDispenserDispenseReagentMessage message)
{
// Ensure that the reagent is something this reagent dispenser can dispense.
if (!GetInventory(reagentDispenser).Contains(message.ReagentId))
return;
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, SharedReagentDispenser.OutputSlotName);
if (outputContainer is not {Valid: true} || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution))
return;
if (_solutionContainerSystem.TryAddReagent(outputContainer.Value, solution, message.ReagentId, (int)reagentDispenser.DispenseAmount, out var dispensedAmount)
&& message.Session.AttachedEntity is not null)
{
_adminLogger.Add(LogType.ChemicalReaction, LogImpact.Medium,
$"{ToPrettyString(message.Session.AttachedEntity.Value):player} dispensed {dispensedAmount}u of {message.ReagentId} into {ToPrettyString(outputContainer.Value):entity}");
}
UpdateUiState(reagentDispenser);
ClickSound(reagentDispenser);
}
private void OnClearContainerSolutionMessage(EntityUid uid, ReagentDispenserComponent reagentDispenser, ReagentDispenserClearContainerSolutionMessage message)
{
var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, SharedReagentDispenser.OutputSlotName);
if (outputContainer is not {Valid: true} || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution))
return;
_solutionContainerSystem.RemoveAllSolution(outputContainer.Value, solution);
UpdateUiState(reagentDispenser);
ClickSound(reagentDispenser);
}
private void ClickSound(ReagentDispenserComponent reagentDispenser)
{
_audioSystem.PlayPvs(reagentDispenser.ClickSound, reagentDispenser.Owner, AudioParams.Default.WithVolume(-2f));
} }
} }
} }

View File

@@ -1,118 +0,0 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.FixedPoint;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.Dispenser
{
/// <summary>
/// Shared class for <c>ReagentDispenserComponent</c>. Provides a way for entities to dispense and remove reagents from other entities with SolutionComponents via a user interface.
/// <para>This is useful for machines such as the chemical dispensers, booze dispensers, or soda dispensers.</para>
/// <para>The chemicals which may be dispensed are defined by specifying a reagent pack. See <see cref="ReagentDispenserInventoryPrototype"/> for more information on that.</para>
/// </summary>
[Virtual]
public class SharedReagentDispenserComponent : Component
{
public const string BeakerSlotId = "ReagentDispenser-beaker";
[DataField("beakerSlot")]
public ItemSlot BeakerSlot = new();
/// <summary>
/// A list of reagents which this may dispense. Defined in yaml prototype, see <see cref="ReagentDispenserInventoryPrototype"/>.
/// </summary>
public readonly List<ReagentDispenserInventoryEntry> Inventory = new();
[Serializable, NetSerializable]
public sealed class ReagentDispenserBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly bool HasPower;
public readonly bool HasBeaker;
public readonly FixedPoint2 BeakerCurrentVolume;
public readonly FixedPoint2 BeakerMaxVolume;
public readonly string ContainerName;
/// <summary>
/// A list of the reagents which this dispenser can dispense.
/// </summary>
public readonly List<ReagentDispenserInventoryEntry> Inventory;
/// <summary>
/// A list of the reagents and their amounts within the beaker/reagent container, if applicable.
/// </summary>
public readonly List<Components.Solution.ReagentQuantity>? ContainerReagents;
public readonly string DispenserName;
public readonly FixedPoint2 SelectedDispenseAmount;
public ReagentDispenserBoundUserInterfaceState(bool hasPower, bool hasBeaker, FixedPoint2 beakerCurrentVolume, FixedPoint2 beakerMaxVolume, string containerName,
List<ReagentDispenserInventoryEntry> inventory, string dispenserName, List<Components.Solution.ReagentQuantity>? containerReagents, FixedPoint2 selectedDispenseAmount)
{
HasPower = hasPower;
HasBeaker = hasBeaker;
BeakerCurrentVolume = beakerCurrentVolume;
BeakerMaxVolume = beakerMaxVolume;
ContainerName = containerName;
Inventory = inventory;
DispenserName = dispenserName;
ContainerReagents = containerReagents;
SelectedDispenseAmount = selectedDispenseAmount;
}
}
/// <summary>
/// Message data sent from client to server when a dispenser ui button is pressed.
/// </summary>
[Serializable, NetSerializable]
public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage
{
public readonly UiButton Button;
public readonly int DispenseIndex; //Index of dispense button / reagent being pressed. Only used when a dispense button is pressed.
public UiButtonPressedMessage(UiButton button, int dispenseIndex)
{
Button = button;
DispenseIndex = dispenseIndex;
}
}
[Serializable, NetSerializable]
public enum ReagentDispenserUiKey
{
Key
}
/// <summary>
/// Used in <see cref="UiButtonPressedMessage"/> to specify which button was pressed.
/// </summary>
public enum UiButton
{
Clear,
SetDispenseAmount1,
SetDispenseAmount5,
SetDispenseAmount10,
SetDispenseAmount15,
SetDispenseAmount20,
SetDispenseAmount25,
SetDispenseAmount30,
SetDispenseAmount50,
SetDispenseAmount100,
/// <summary>
/// Used when any dispense button is pressed. Such as "Carbon", or "Oxygen" buttons on the chem dispenser.
/// The index of the reagent attached to that dispense button is sent as <see cref="UiButtonPressedMessage.DispenseIndex"/>.
/// </summary>
Dispense
}
/// <summary>
/// Information about a reagent which the dispenser can dispense.
/// </summary>
[Serializable, NetSerializable]
public struct ReagentDispenserInventoryEntry
{
public readonly string ID;
public ReagentDispenserInventoryEntry(string id)
{
ID = id;
}
}
}
}

View File

@@ -1,30 +0,0 @@
using JetBrains.Annotations;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Chemistry.Dispenser;
namespace Content.Shared.Chemistry.EntitySystems
{
[UsedImplicitly]
public abstract class SharedReagentDispenserSystem : EntitySystem
{
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedReagentDispenserComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<SharedReagentDispenserComponent, ComponentRemove>(OnComponentRemove);
}
private void OnComponentInit(EntityUid uid, SharedReagentDispenserComponent component, ComponentInit args)
{
_itemSlotsSystem.AddItemSlot(uid, SharedReagentDispenserComponent.BeakerSlotId, component.BeakerSlot);
}
private void OnComponentRemove(EntityUid uid, SharedReagentDispenserComponent component, ComponentRemove args)
{
_itemSlotsSystem.RemoveItemSlot(uid, component.BeakerSlot);
}
}
}

View File

@@ -159,7 +159,6 @@ namespace Content.Shared.Chemistry
/// A list of the reagents and their amounts within the buffer, if applicable. /// A list of the reagents and their amounts within the buffer, if applicable.
/// </summary> /// </summary>
public readonly IReadOnlyList<Solution.ReagentQuantity> BufferReagents; public readonly IReadOnlyList<Solution.ReagentQuantity> BufferReagents;
public readonly string DispenserName;
public readonly ChemMasterMode Mode; public readonly ChemMasterMode Mode;
@@ -171,14 +170,12 @@ namespace Content.Shared.Chemistry
public readonly bool UpdateLabel; public readonly bool UpdateLabel;
public ChemMasterBoundUserInterfaceState( public ChemMasterBoundUserInterfaceState(
ChemMasterMode mode, string dispenserName, ChemMasterMode mode, ContainerInfo? inputContainerInfo, ContainerInfo? outputContainerInfo,
ContainerInfo? inputContainerInfo, ContainerInfo? outputContainerInfo,
IReadOnlyList<Solution.ReagentQuantity> bufferReagents, FixedPoint2 bufferCurrentVolume, IReadOnlyList<Solution.ReagentQuantity> bufferReagents, FixedPoint2 bufferCurrentVolume,
uint selectedPillType, uint pillDosageLimit, bool updateLabel) uint selectedPillType, uint pillDosageLimit, bool updateLabel)
{ {
InputContainerInfo = inputContainerInfo; InputContainerInfo = inputContainerInfo;
OutputContainerInfo = outputContainerInfo; OutputContainerInfo = outputContainerInfo;
DispenserName = dispenserName;
BufferReagents = bufferReagents; BufferReagents = bufferReagents;
Mode = mode; Mode = mode;
BufferCurrentVolume = bufferCurrentVolume; BufferCurrentVolume = bufferCurrentVolume;

View File

@@ -0,0 +1,78 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
{
/// <summary>
/// This class holds constants that are shared between client and server.
/// </summary>
public sealed class SharedReagentDispenser
{
public const string OutputSlotName = "beakerSlot";
}
[Serializable, NetSerializable]
public sealed class ReagentDispenserSetDispenseAmountMessage : BoundUserInterfaceMessage
{
public readonly ReagentDispenserDispenseAmount ReagentDispenserDispenseAmount;
public ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount amount)
{
ReagentDispenserDispenseAmount = amount;
}
}
[Serializable, NetSerializable]
public sealed class ReagentDispenserDispenseReagentMessage : BoundUserInterfaceMessage
{
public readonly string ReagentId;
public ReagentDispenserDispenseReagentMessage(string reagentId)
{
ReagentId = reagentId;
}
}
[Serializable, NetSerializable]
public sealed class ReagentDispenserClearContainerSolutionMessage : BoundUserInterfaceMessage
{
}
public enum ReagentDispenserDispenseAmount
{
U1 = 1,
U5 = 5,
U10 = 10,
U15 = 15,
U20 = 20,
U25 = 25,
U30 = 30,
U50 = 50,
U100 = 100,
}
[Serializable, NetSerializable]
public sealed class ReagentDispenserBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly ContainerInfo? OutputContainer;
/// <summary>
/// A list of the reagents which this dispenser can dispense.
/// </summary>
public readonly List<string> Inventory;
public readonly ReagentDispenserDispenseAmount SelectedDispenseAmount;
public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, List<string> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount)
{
OutputContainer = outputContainer;
Inventory = inventory;
SelectedDispenseAmount = selectedDispenseAmount;
}
}
[Serializable, NetSerializable]
public enum ReagentDispenserUiKey
{
Key
}
}

View File

@@ -532,9 +532,10 @@ namespace Content.Shared.Containers.ItemSlots
/// <summary> /// <summary>
/// Get the contents of some item slot. /// Get the contents of some item slot.
/// </summary> /// </summary>
public EntityUid? GetItem(EntityUid uid, string id, ItemSlotsComponent? itemSlots = null) /// <returns>The item in the slot, or null if the slot is empty or the entity doesn't have an <see cref="ItemSlotsComponent"/>.</returns>
public EntityUid? GetItemOrNull(EntityUid uid, string id, ItemSlotsComponent? itemSlots = null)
{ {
if (!Resolve(uid, ref itemSlots)) if (!Resolve(uid, ref itemSlots, logMissing: false))
return null; return null;
return itemSlots.Slots.GetValueOrDefault(id)?.Item; return itemSlots.Slots.GetValueOrDefault(id)?.Item;

View File

@@ -48,16 +48,17 @@
sound: sound:
path: /Audio/Effects/metalbreak.ogg path: /Audio/Effects/metalbreak.ogg
- type: ReagentDispenser - type: ReagentDispenser
- type: ItemSlots
slots:
beakerSlot: beakerSlot:
whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message
whitelist: whitelist:
components: components:
- FitsInDispenser - FitsInDispenser
- type: ItemSlots
- type: ContainerContainer - type: ContainerContainer
containers: containers:
machine_board: !type:Container machine_board: !type:Container
machine_parts: !type:Container machine_parts: !type:Container
ReagentDispenser-beaker: !type:ContainerSlot beakerSlot: !type:ContainerSlot
- type: StaticPrice - type: StaticPrice
price: 1000 price: 1000