MaterialStorageSystem.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. using System.Linq;
  2. using Content.Server.Administration.Logs;
  3. using Content.Shared.Materials;
  4. using Content.Shared.Popups;
  5. using Content.Shared.Stacks;
  6. using Content.Server.Power.Components;
  7. using Content.Server.Stack;
  8. using Content.Shared.ActionBlocker;
  9. using Content.Shared.Construction;
  10. using Content.Shared.Database;
  11. using JetBrains.Annotations;
  12. using Robust.Shared.Audio.Systems;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Prototypes;
  15. namespace Content.Server.Materials;
  16. /// <summary>
  17. /// This handles <see cref="SharedMaterialStorageSystem"/>
  18. /// </summary>
  19. public sealed class MaterialStorageSystem : SharedMaterialStorageSystem
  20. {
  21. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  22. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  23. [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
  24. [Dependency] private readonly SharedAudioSystem _audio = default!;
  25. [Dependency] private readonly SharedPopupSystem _popup = default!;
  26. [Dependency] private readonly StackSystem _stackSystem = default!;
  27. public override void Initialize()
  28. {
  29. base.Initialize();
  30. SubscribeLocalEvent<MaterialStorageComponent, MachineDeconstructedEvent>(OnDeconstructed);
  31. SubscribeAllEvent<EjectMaterialMessage>(OnEjectMessage);
  32. }
  33. private void OnDeconstructed(EntityUid uid, MaterialStorageComponent component, MachineDeconstructedEvent args)
  34. {
  35. if (!component.DropOnDeconstruct)
  36. return;
  37. foreach (var (material, amount) in component.Storage)
  38. {
  39. SpawnMultipleFromMaterial(amount, material, Transform(uid).Coordinates);
  40. }
  41. }
  42. private void OnEjectMessage(EjectMaterialMessage msg, EntitySessionEventArgs args)
  43. {
  44. if (args.SenderSession.AttachedEntity is not { } player)
  45. return;
  46. var uid = GetEntity(msg.Entity);
  47. if (!TryComp<MaterialStorageComponent>(uid, out var component))
  48. return;
  49. if (!Exists(uid))
  50. return;
  51. if (!_actionBlocker.CanInteract(player, uid))
  52. return;
  53. if (!component.CanEjectStoredMaterials || !_prototypeManager.TryIndex<MaterialPrototype>(msg.Material, out var material))
  54. return;
  55. var volume = 0;
  56. if (material.StackEntity != null)
  57. {
  58. if (!_prototypeManager.Index<EntityPrototype>(material.StackEntity).TryGetComponent<PhysicalCompositionComponent>(out var composition, EntityManager.ComponentFactory))
  59. return;
  60. var volumePerSheet = composition.MaterialComposition.FirstOrDefault(kvp => kvp.Key == msg.Material).Value;
  61. var sheetsToExtract = Math.Min(msg.SheetsToExtract, _stackSystem.GetMaxCount(material.StackEntity));
  62. volume = sheetsToExtract * volumePerSheet;
  63. }
  64. if (volume <= 0 || !TryChangeMaterialAmount(uid, msg.Material, -volume))
  65. return;
  66. var mats = SpawnMultipleFromMaterial(volume, material, Transform(uid).Coordinates, out _);
  67. foreach (var mat in mats.Where(mat => !TerminatingOrDeleted(mat)))
  68. {
  69. _stackSystem.TryMergeToContacts(mat);
  70. }
  71. }
  72. public override bool TryInsertMaterialEntity(EntityUid user,
  73. EntityUid toInsert,
  74. EntityUid receiver,
  75. MaterialStorageComponent? storage = null,
  76. MaterialComponent? material = null,
  77. PhysicalCompositionComponent? composition = null)
  78. {
  79. if (!Resolve(receiver, ref storage) || !Resolve(toInsert, ref material, ref composition, false))
  80. return false;
  81. if (TryComp<ApcPowerReceiverComponent>(receiver, out var power) && !power.Powered)
  82. return false;
  83. if (!base.TryInsertMaterialEntity(user, toInsert, receiver, storage, material, composition))
  84. return false;
  85. _audio.PlayPvs(storage.InsertingSound, receiver);
  86. _popup.PopupEntity(Loc.GetString("machine-insert-item", ("user", user), ("machine", receiver),
  87. ("item", toInsert)), receiver);
  88. QueueDel(toInsert);
  89. // Logging
  90. TryComp<StackComponent>(toInsert, out var stack);
  91. var count = stack?.Count ?? 1;
  92. _adminLogger.Add(LogType.Action, LogImpact.Low,
  93. $"{ToPrettyString(user):player} inserted {count} {ToPrettyString(toInsert):inserted} into {ToPrettyString(receiver):receiver}");
  94. return true;
  95. }
  96. /// <summary>
  97. /// Spawn an amount of a material in stack entities.
  98. /// Note the 'amount' is material dependent.
  99. /// 1 biomass = 1 biomass in its stack,
  100. /// but 100 plasma = 1 sheet of plasma, etc.
  101. /// </summary>
  102. public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates)
  103. {
  104. return SpawnMultipleFromMaterial(amount, material, coordinates, out _);
  105. }
  106. /// <summary>
  107. /// Spawn an amount of a material in stack entities.
  108. /// Note the 'amount' is material dependent.
  109. /// 1 biomass = 1 biomass in its stack,
  110. /// but 100 plasma = 1 sheet of plasma, etc.
  111. /// </summary>
  112. public List<EntityUid> SpawnMultipleFromMaterial(int amount, string material, EntityCoordinates coordinates, out int overflowMaterial)
  113. {
  114. overflowMaterial = 0;
  115. if (!_prototypeManager.TryIndex<MaterialPrototype>(material, out var stackType))
  116. {
  117. Log.Error("Failed to index material prototype " + material);
  118. return new List<EntityUid>();
  119. }
  120. return SpawnMultipleFromMaterial(amount, stackType, coordinates, out overflowMaterial);
  121. }
  122. /// <summary>
  123. /// Spawn an amount of a material in stack entities.
  124. /// Note the 'amount' is material dependent.
  125. /// 1 biomass = 1 biomass in its stack,
  126. /// but 100 plasma = 1 sheet of plasma, etc.
  127. /// </summary>
  128. [PublicAPI]
  129. public List<EntityUid> SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates)
  130. {
  131. return SpawnMultipleFromMaterial(amount, materialProto, coordinates, out _);
  132. }
  133. /// <summary>
  134. /// Spawn an amount of a material in stack entities.
  135. /// Note the 'amount' is material dependent.
  136. /// 1 biomass = 1 biomass in its stack,
  137. /// but 100 plasma = 1 sheet of plasma, etc.
  138. /// </summary>
  139. public List<EntityUid> SpawnMultipleFromMaterial(int amount, MaterialPrototype materialProto, EntityCoordinates coordinates, out int overflowMaterial)
  140. {
  141. overflowMaterial = 0;
  142. if (amount <= 0 || materialProto.StackEntity == null)
  143. return new List<EntityUid>();
  144. var entProto = _prototypeManager.Index<EntityPrototype>(materialProto.StackEntity);
  145. if (!entProto.TryGetComponent<PhysicalCompositionComponent>(out var composition, EntityManager.ComponentFactory))
  146. return new List<EntityUid>();
  147. var materialPerStack = composition.MaterialComposition[materialProto.ID];
  148. var amountToSpawn = amount / materialPerStack;
  149. overflowMaterial = amount - amountToSpawn * materialPerStack;
  150. if (amountToSpawn == 0)
  151. return new List<EntityUid>();
  152. return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates);
  153. }
  154. /// <summary>
  155. /// Eject a material out of this storage. The internal counts are updated.
  156. /// Material that cannot be ejected stays in storage. (e.g. only have 50 but a sheet needs 100).
  157. /// </summary>
  158. /// <param name="entity">The entity with storage to eject from.</param>
  159. /// <param name="material">The material prototype to eject.</param>
  160. /// <param name="maxAmount">The maximum amount to eject. If not given, as much as possible is ejected.</param>
  161. /// <param name="coordinates">The position where to spawn the created sheets. If not given, they're spawned next to the entity.</param>
  162. /// <param name="component">The storage component on <paramref name="entity"/>. Resolved automatically if not given.</param>
  163. /// <returns>The stack entities that were spawned.</returns>
  164. public List<EntityUid> EjectMaterial(
  165. EntityUid entity,
  166. string material,
  167. int? maxAmount = null,
  168. EntityCoordinates? coordinates = null,
  169. MaterialStorageComponent? component = null)
  170. {
  171. if (!Resolve(entity, ref component))
  172. return new List<EntityUid>();
  173. coordinates ??= Transform(entity).Coordinates;
  174. var amount = GetMaterialAmount(entity, material, component);
  175. if (maxAmount != null)
  176. amount = Math.Min(maxAmount.Value, amount);
  177. var spawned = SpawnMultipleFromMaterial(amount, material, coordinates.Value, out var overflow);
  178. TryChangeMaterialAmount(entity, material, -(amount - overflow), component);
  179. return spawned;
  180. }
  181. /// <summary>
  182. /// Eject all material stored in an entity, with the same mechanics as <see cref="EjectMaterial"/>.
  183. /// </summary>
  184. /// <param name="entity">The entity with storage to eject from.</param>
  185. /// <param name="coordinates">The position where to spawn the created sheets. If not given, they're spawned next to the entity.</param>
  186. /// <param name="component">The storage component on <paramref name="entity"/>. Resolved automatically if not given.</param>
  187. /// <returns>The stack entities that were spawned.</returns>
  188. public List<EntityUid> EjectAllMaterial(
  189. EntityUid entity,
  190. EntityCoordinates? coordinates = null,
  191. MaterialStorageComponent? component = null)
  192. {
  193. if (!Resolve(entity, ref component))
  194. return new List<EntityUid>();
  195. coordinates ??= Transform(entity).Coordinates;
  196. var allSpawned = new List<EntityUid>();
  197. foreach (var material in component.Storage.Keys.ToArray())
  198. {
  199. var spawned = EjectMaterial(entity, material, null, coordinates, component);
  200. allSpawned.AddRange(spawned);
  201. }
  202. return allSpawned;
  203. }
  204. }