1
0

ReagentDispenserSystem.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. using Content.Server.Chemistry.Components;
  2. using Content.Server.Chemistry.Containers.EntitySystems;
  3. using Content.Shared.Chemistry;
  4. using Content.Shared.Chemistry.Dispenser;
  5. using Content.Shared.Chemistry.EntitySystems;
  6. using Content.Shared.Containers.ItemSlots;
  7. using Content.Shared.FixedPoint;
  8. using Content.Shared.Nutrition.EntitySystems;
  9. using JetBrains.Annotations;
  10. using Robust.Server.Audio;
  11. using Robust.Server.GameObjects;
  12. using Robust.Shared.Audio;
  13. using Robust.Shared.Containers;
  14. using Robust.Shared.Prototypes;
  15. using Content.Shared.Labels.Components;
  16. namespace Content.Server.Chemistry.EntitySystems
  17. {
  18. /// <summary>
  19. /// Contains all the server-side logic for reagent dispensers.
  20. /// <seealso cref="ReagentDispenserComponent"/>
  21. /// </summary>
  22. [UsedImplicitly]
  23. public sealed class ReagentDispenserSystem : EntitySystem
  24. {
  25. [Dependency] private readonly AudioSystem _audioSystem = default!;
  26. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  27. [Dependency] private readonly SolutionTransferSystem _solutionTransferSystem = default!;
  28. [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
  29. [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
  30. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  31. [Dependency] private readonly OpenableSystem _openable = default!;
  32. public override void Initialize()
  33. {
  34. base.Initialize();
  35. SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>(SubscribeUpdateUiState);
  36. SubscribeLocalEvent<ReagentDispenserComponent, SolutionContainerChangedEvent>(SubscribeUpdateUiState);
  37. SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
  38. SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
  39. SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
  40. SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserSetDispenseAmountMessage>(OnSetDispenseAmountMessage);
  41. SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserDispenseReagentMessage>(OnDispenseReagentMessage);
  42. SubscribeLocalEvent<ReagentDispenserComponent, ReagentDispenserClearContainerSolutionMessage>(OnClearContainerSolutionMessage);
  43. SubscribeLocalEvent<ReagentDispenserComponent, MapInitEvent>(OnMapInit, before: new []{typeof(ItemSlotsSystem)});
  44. }
  45. private void SubscribeUpdateUiState<T>(Entity<ReagentDispenserComponent> ent, ref T ev)
  46. {
  47. UpdateUiState(ent);
  48. }
  49. private void UpdateUiState(Entity<ReagentDispenserComponent> reagentDispenser)
  50. {
  51. var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
  52. var outputContainerInfo = BuildOutputContainerInfo(outputContainer);
  53. var inventory = GetInventory(reagentDispenser);
  54. var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, GetNetEntity(outputContainer), inventory, reagentDispenser.Comp.DispenseAmount);
  55. _userInterfaceSystem.SetUiState(reagentDispenser.Owner, ReagentDispenserUiKey.Key, state);
  56. }
  57. private ContainerInfo? BuildOutputContainerInfo(EntityUid? container)
  58. {
  59. if (container is not { Valid: true })
  60. return null;
  61. if (_solutionContainerSystem.TryGetFitsInDispenser(container.Value, out _, out var solution))
  62. {
  63. return new ContainerInfo(Name(container.Value), solution.Volume, solution.MaxVolume)
  64. {
  65. Reagents = solution.Contents
  66. };
  67. }
  68. return null;
  69. }
  70. private List<ReagentInventoryItem> GetInventory(Entity<ReagentDispenserComponent> reagentDispenser)
  71. {
  72. var inventory = new List<ReagentInventoryItem>();
  73. for (var i = 0; i < reagentDispenser.Comp.NumSlots; i++)
  74. {
  75. var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
  76. var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser.Owner, storageSlotId);
  77. // Set label from manually-applied label, or metadata if unavailable
  78. string reagentLabel;
  79. if (TryComp<LabelComponent>(storedContainer, out var label) && !string.IsNullOrEmpty(label.CurrentLabel))
  80. reagentLabel = label.CurrentLabel;
  81. else if (storedContainer != null)
  82. reagentLabel = Name(storedContainer.Value);
  83. else
  84. continue;
  85. // Get volume remaining and color of solution
  86. FixedPoint2 quantity = 0f;
  87. var reagentColor = Color.White;
  88. if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol))
  89. {
  90. quantity = sol.Volume;
  91. reagentColor = sol.GetColor(_prototypeManager);
  92. }
  93. inventory.Add(new ReagentInventoryItem(storageSlotId, reagentLabel, quantity, reagentColor));
  94. }
  95. return inventory;
  96. }
  97. private void OnSetDispenseAmountMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserSetDispenseAmountMessage message)
  98. {
  99. reagentDispenser.Comp.DispenseAmount = message.ReagentDispenserDispenseAmount;
  100. UpdateUiState(reagentDispenser);
  101. ClickSound(reagentDispenser);
  102. }
  103. private void OnDispenseReagentMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserDispenseReagentMessage message)
  104. {
  105. // Ensure that the reagent is something this reagent dispenser can dispense.
  106. var storedContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, message.SlotId);
  107. if (storedContainer == null)
  108. return;
  109. var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
  110. if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
  111. return;
  112. if (_solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out var src, out _) &&
  113. _solutionContainerSystem.TryGetRefillableSolution(outputContainer.Value, out var dst, out _))
  114. {
  115. // force open container, if applicable, to avoid confusing people on why it doesn't dispense
  116. _openable.SetOpen(storedContainer.Value, true);
  117. _solutionTransferSystem.Transfer(reagentDispenser,
  118. storedContainer.Value, src.Value,
  119. outputContainer.Value, dst.Value,
  120. (int)reagentDispenser.Comp.DispenseAmount);
  121. }
  122. UpdateUiState(reagentDispenser);
  123. ClickSound(reagentDispenser);
  124. }
  125. private void OnClearContainerSolutionMessage(Entity<ReagentDispenserComponent> reagentDispenser, ref ReagentDispenserClearContainerSolutionMessage message)
  126. {
  127. var outputContainer = _itemSlotsSystem.GetItemOrNull(reagentDispenser, SharedReagentDispenser.OutputSlotName);
  128. if (outputContainer is not { Valid: true } || !_solutionContainerSystem.TryGetFitsInDispenser(outputContainer.Value, out var solution, out _))
  129. return;
  130. _solutionContainerSystem.RemoveAllSolution(solution.Value);
  131. UpdateUiState(reagentDispenser);
  132. ClickSound(reagentDispenser);
  133. }
  134. private void ClickSound(Entity<ReagentDispenserComponent> reagentDispenser)
  135. {
  136. _audioSystem.PlayPvs(reagentDispenser.Comp.ClickSound, reagentDispenser, AudioParams.Default.WithVolume(-2f));
  137. }
  138. /// <summary>
  139. /// Automatically generate storage slots for all NumSlots, and fill them with their initial chemicals.
  140. /// The actual spawning of entities happens in ItemSlotsSystem's MapInit.
  141. /// </summary>
  142. private void OnMapInit(EntityUid uid, ReagentDispenserComponent component, MapInitEvent args)
  143. {
  144. // Get list of pre-loaded containers
  145. List<string> preLoad = new List<string>();
  146. if (component.PackPrototypeId is not null
  147. && _prototypeManager.TryIndex(component.PackPrototypeId, out ReagentDispenserInventoryPrototype? packPrototype))
  148. {
  149. preLoad.AddRange(packPrototype.Inventory);
  150. }
  151. // Populate storage slots with base storage slot whitelist
  152. for (var i = 0; i < component.NumSlots; i++)
  153. {
  154. var storageSlotId = ReagentDispenserComponent.BaseStorageSlotId + i;
  155. ItemSlot storageComponent = new();
  156. storageComponent.Whitelist = component.StorageWhitelist;
  157. storageComponent.Swap = false;
  158. storageComponent.EjectOnBreak = true;
  159. // Check corresponding index in pre-loaded container (if exists) and set starting item
  160. if (i < preLoad.Count)
  161. storageComponent.StartingItem = preLoad[i];
  162. component.StorageSlotIds.Add(storageSlotId);
  163. component.StorageSlots.Add(storageComponent);
  164. component.StorageSlots[i].Name = "Storage Slot " + (i+1);
  165. _itemSlotsSystem.AddItemSlot(uid, component.StorageSlotIds[i], component.StorageSlots[i]);
  166. }
  167. _itemSlotsSystem.AddItemSlot(uid, SharedReagentDispenser.OutputSlotName, component.BeakerSlot);
  168. }
  169. }
  170. }