using System.Linq; using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Chemistry; 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.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Content.Shared.Labels.Components; using Content.Shared.Storage; using Content.Server.Hands.Systems; using Content.Shared.Chemistry.Reagent; // Frontier using Content.Shared.Verbs; // Frontier using Content.Shared.Examine; // Frontier using Content.Server.Construction; // Frontier using Content.Shared.Labels.EntitySystems; // Frontier namespace Content.Server.Chemistry.EntitySystems { /// /// Contains all the server-side logic for reagent dispensers. /// /// [UsedImplicitly] public sealed class ReagentDispenserSystem : EntitySystem { [Dependency] private readonly AudioSystem _audioSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _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 OpenableSystem _openable = default!; [Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly LabelSystem _label = default!; // Frontier [Dependency] private readonly SharedContainerSystem _containers = default!; // Frontier public override void Initialize() { base.Initialize(); SubscribeLocalEvent(SubscribeUpdateUiState); SubscribeLocalEvent(SubscribeUpdateUiState); // SubscribeLocalEvent(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]); // Frontier SubscribeLocalEvent(OnEntInserted, after: [typeof(SharedStorageSystem)]); // Frontier: Auto label on insert SubscribeLocalEvent(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]); SubscribeLocalEvent(SubscribeUpdateUiState); SubscribeLocalEvent>(OnAlternateVerb); // Frontier SubscribeLocalEvent(OnExamined); // Frontier SubscribeLocalEvent(OnSetDispenseAmountMessage); SubscribeLocalEvent(OnDispenseReagentMessage); SubscribeLocalEvent(OnEjectReagentMessage); SubscribeLocalEvent(OnClearContainerSolutionMessage); SubscribeLocalEvent(OnMapInit, before: new[] { typeof(ItemSlotsSystem) }); } private void SubscribeUpdateUiState(Entity ent, ref T ev) { UpdateUiState(ent); } // Frontier: auto-label on insert private void OnEntInserted(Entity ent, ref EntInsertedIntoContainerMessage ev) { if (ent.Comp.AutoLabel && _solutionContainerSystem.TryGetDrainableSolution(ev.Entity, out _, out var sol)) { var reagentId = sol.GetPrimaryReagentId(); if (reagentId != null && _prototypeManager.TryIndex(reagentId.Value.Prototype, out var reagent)) { var reagentQuantity = sol.GetReagentQuantity(reagentId.Value); var totalQuantity = sol.Volume; if (reagentQuantity == totalQuantity) _label.Label(ev.Entity, reagent.LocalizedName); else _label.Label(ev.Entity, Loc.GetString("reagent-dispenser-component-impure-auto-label", ("reagent", reagent.LocalizedName), ("purity", 100.0f * reagentQuantity / totalQuantity))); } } UpdateUiState(ent); } private void OnAlternateVerb(Entity ent, ref GetVerbsEvent args) { if (!ent.Comp.CanAutoLabel) return; args.Verbs.Add(new AlternativeVerb() { Act = () => { SetAutoLabel(ent, !ent.Comp.AutoLabel); }, Text = ent.Comp.AutoLabel ? Loc.GetString("reagent-dispenser-component-set-auto-label-off-verb") : Loc.GetString("reagent-dispenser-component-set-auto-label-on-verb"), Priority = -1, //Not important, low priority. }); } private void SetAutoLabel(Entity ent, bool autoLabel) { if (!ent.Comp.CanAutoLabel) return; ent.Comp.AutoLabel = autoLabel; } private void OnExamined(Entity ent, ref ExaminedEvent args) { if (!args.IsInDetailsRange || !ent.Comp.CanAutoLabel) return; if (ent.Comp.AutoLabel) args.PushMarkup(Loc.GetString("reagent-dispenser-component-examine-auto-label-on")); else args.PushMarkup(Loc.GetString("reagent-dispenser-component-examine-auto-label-off")); } // End Frontier private void UpdateUiState(Entity reagentDispenser) { var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName); var outputContainerInfo = BuildOutputContainerInfo(outputContainer); var inventory = GetInventory(reagentDispenser); var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, GetNetEntity(outputContainer), inventory, reagentDispenser.Comp.DispenseAmount); _userInterfaceSystem.SetUiState(reagentDispenser.Owner, ReagentDispenserUiKey.Key, state); } private ContainerInfo? BuildOutputContainerInfo(EntityUid? container) { if (container is not { Valid: true }) return null; if (_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out _, out var solution)) { return new ContainerInfo(Name(container.Value), solution.Volume, solution.MaxVolume) { Reagents = solution.Contents }; } return null; } private List GetInventory(Entity reagentDispenser) { if (!TryComp(reagentDispenser.Owner, out var storage)) { return []; } var inventory = new List(); foreach (var (storedContainer, storageLocation) in storage.StoredItems) { string reagentLabel; if (TryComp(storedContainer, out var label) && !string.IsNullOrEmpty(label.CurrentLabel)) reagentLabel = label.CurrentLabel; else reagentLabel = Name(storedContainer); // Get volume remaining and color of solution FixedPoint2 quantity = 0f; var reagentColor = Color.White; if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer, out _, out var sol)) { quantity = sol.Volume; reagentColor = sol.GetColor(_prototypeManager); } inventory.Add(new ReagentInventoryItem(storageLocation, reagentLabel, quantity, reagentColor)); } return inventory; } private void OnSetDispenseAmountMessage(Entity reagentDispenser, ref ReagentDispenserSetDispenseAmountMessage message) { reagentDispenser.Comp.DispenseAmount = message.ReagentDispenserDispenseAmount; UpdateUiState(reagentDispenser); ClickSound(reagentDispenser); } private void OnDispenseReagentMessage(Entity reagentDispenser, ref ReagentDispenserDispenseReagentMessage message) { if (!TryComp(reagentDispenser.Owner, out var storage)) { return; } // Ensure that the reagent is something this reagent dispenser can dispense. var storageLocation = message.StorageLocation; var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key; if (storedContainer == EntityUid.Invalid) 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.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, true); _solutionTransferSystem.Transfer(reagentDispenser, storedContainer, src.Value, outputContainer.Value, dst.Value, (int)reagentDispenser.Comp.DispenseAmount); } UpdateUiState(reagentDispenser); ClickSound(reagentDispenser); } private void OnEjectReagentMessage(Entity reagentDispenser, ref ReagentDispenserEjectContainerMessage message) { if (!TryComp(reagentDispenser.Owner, out var storage)) { return; } var storageLocation = message.StorageLocation; var storedContainer = storage.StoredItems.FirstOrDefault(kvp => kvp.Value == storageLocation).Key; if (storedContainer == EntityUid.Invalid) return; _handsSystem.TryPickupAnyHand(message.Actor, storedContainer); } private void OnClearContainerSolutionMessage(Entity reagentDispenser, ref ReagentDispenserClearContainerSolutionMessage message) { var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName); if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _)) return; _solutionContainerSystem.RemoveAllSolution(solution.Value); UpdateUiState(reagentDispenser); ClickSound(reagentDispenser); } private void ClickSound(Entity reagentDispenser) { _audioSystem.PlayPvs(reagentDispenser.Comp.ClickSound, reagentDispenser, AudioParams.Default.WithVolume(-2f)); } /// /// Initializes the beaker slot /// private void OnMapInit(Entity ent, ref MapInitEvent args) { // Frontier: set auto-labeller ent.Comp.AutoLabel = ent.Comp.CanAutoLabel; // Frontier: set auto-labeller _itemSlotsSystem.AddItemSlot(ent.Owner, SharedReagentDispenser.OutputSlotName, ent.Comp.BeakerSlot); } } }