SharedMaterialReclaimerSystem.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using System.Linq;
  2. using Content.Shared.Administration.Logs;
  3. using Content.Shared.Audio;
  4. using Content.Shared.Body.Components;
  5. using Content.Shared.Database;
  6. using Content.Shared.Emag.Components;
  7. using Content.Shared.Emag.Systems;
  8. using Content.Shared.Examine;
  9. using Content.Shared.Mobs.Components;
  10. using Content.Shared.Stacks;
  11. using Content.Shared.Whitelist;
  12. using Robust.Shared.Audio.Systems;
  13. using Robust.Shared.Containers;
  14. using Robust.Shared.Map;
  15. using Robust.Shared.Physics.Events;
  16. using Robust.Shared.Timing;
  17. namespace Content.Shared.Materials;
  18. /// <summary>
  19. /// Handles interactions and logic related to <see cref="MaterialReclaimerComponent"/>,
  20. /// <see cref="CollideMaterialReclaimerComponent"/>, and <see cref="ActiveMaterialReclaimerComponent"/>.
  21. /// </summary>
  22. public abstract class SharedMaterialReclaimerSystem : EntitySystem
  23. {
  24. [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
  25. [Dependency] protected readonly IGameTiming Timing = default!;
  26. [Dependency] protected readonly SharedAmbientSoundSystem AmbientSound = default!;
  27. [Dependency] private readonly SharedAudioSystem _audio = default!;
  28. [Dependency] protected readonly SharedContainerSystem Container = default!;
  29. [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
  30. [Dependency] private readonly EmagSystem _emag = default!;
  31. public const string ActiveReclaimerContainerId = "active-material-reclaimer-container";
  32. /// <inheritdoc/>
  33. public override void Initialize()
  34. {
  35. SubscribeLocalEvent<MaterialReclaimerComponent, ComponentShutdown>(OnShutdown);
  36. SubscribeLocalEvent<MaterialReclaimerComponent, ExaminedEvent>(OnExamined);
  37. SubscribeLocalEvent<MaterialReclaimerComponent, GotEmaggedEvent>(OnEmagged);
  38. SubscribeLocalEvent<MaterialReclaimerComponent, MapInitEvent>(OnMapInit);
  39. SubscribeLocalEvent<CollideMaterialReclaimerComponent, StartCollideEvent>(OnCollide);
  40. SubscribeLocalEvent<ActiveMaterialReclaimerComponent, ComponentStartup>(OnActiveStartup);
  41. }
  42. private void OnMapInit(EntityUid uid, MaterialReclaimerComponent component, MapInitEvent args)
  43. {
  44. component.NextSound = Timing.CurTime;
  45. }
  46. private void OnShutdown(EntityUid uid, MaterialReclaimerComponent component, ComponentShutdown args)
  47. {
  48. _audio.Stop(component.Stream);
  49. }
  50. private void OnExamined(EntityUid uid, MaterialReclaimerComponent component, ExaminedEvent args)
  51. {
  52. args.PushMarkup(Loc.GetString("recycler-count-items", ("items", component.ItemsProcessed)));
  53. }
  54. private void OnEmagged(EntityUid uid, MaterialReclaimerComponent component, ref GotEmaggedEvent args)
  55. {
  56. if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
  57. return;
  58. if (_emag.CheckFlag(uid, EmagType.Interaction))
  59. return;
  60. args.Handled = true;
  61. }
  62. private void OnCollide(EntityUid uid, CollideMaterialReclaimerComponent component, ref StartCollideEvent args)
  63. {
  64. if (args.OurFixtureId != component.FixtureId)
  65. return;
  66. if (!TryComp<MaterialReclaimerComponent>(uid, out var reclaimer))
  67. return;
  68. TryStartProcessItem(uid, args.OtherEntity, reclaimer);
  69. }
  70. private void OnActiveStartup(EntityUid uid, ActiveMaterialReclaimerComponent component, ComponentStartup args)
  71. {
  72. component.ReclaimingContainer = Container.EnsureContainer<Container>(uid, ActiveReclaimerContainerId);
  73. }
  74. /// <summary>
  75. /// Tries to start processing an item via a <see cref="MaterialReclaimerComponent"/>.
  76. /// </summary>
  77. public bool TryStartProcessItem(EntityUid uid, EntityUid item, MaterialReclaimerComponent? component = null, EntityUid? user = null)
  78. {
  79. if (!Resolve(uid, ref component))
  80. return false;
  81. if (!CanStart(uid, component))
  82. return false;
  83. if (HasComp<MobStateComponent>(item) && !CanGib(uid, item, component)) // whitelist? We be gibbing, boy!
  84. return false;
  85. if (_whitelistSystem.IsWhitelistFail(component.Whitelist, item) ||
  86. _whitelistSystem.IsBlacklistPass(component.Blacklist, item))
  87. return false;
  88. if (Container.TryGetContainingContainer((item, null, null), out _) && !Container.TryRemoveFromContainer(item))
  89. return false;
  90. if (user != null)
  91. {
  92. _adminLog.Add(LogType.Action,
  93. LogImpact.High,
  94. $"{ToPrettyString(user.Value):player} destroyed {ToPrettyString(item)} in the material reclaimer, {ToPrettyString(uid)}");
  95. }
  96. if (Timing.CurTime > component.NextSound)
  97. {
  98. component.Stream = _audio.PlayPredicted(component.Sound, uid, user)?.Entity;
  99. component.NextSound = Timing.CurTime + component.SoundCooldown;
  100. }
  101. var reclaimedEvent = new GotReclaimedEvent(Transform(uid).Coordinates);
  102. RaiseLocalEvent(item, ref reclaimedEvent);
  103. var duration = GetReclaimingDuration(uid, item, component);
  104. // if it's instant, don't bother with all the active comp stuff.
  105. if (duration == TimeSpan.Zero)
  106. {
  107. Reclaim(uid, item, 1, component);
  108. return true;
  109. }
  110. var active = EnsureComp<ActiveMaterialReclaimerComponent>(uid);
  111. active.Duration = duration;
  112. active.EndTime = Timing.CurTime + duration;
  113. Container.Insert(item, active.ReclaimingContainer);
  114. return true;
  115. }
  116. /// <summary>
  117. /// Finishes processing an item, freeing up the the reclaimer.
  118. /// </summary>
  119. /// <remarks>
  120. /// This doesn't reclaim the entity itself, but rather ends the formal
  121. /// process started with <see cref="ActiveMaterialReclaimerComponent"/>.
  122. /// The actual reclaiming happens in <see cref="Reclaim"/>
  123. /// </remarks>
  124. public virtual bool TryFinishProcessItem(EntityUid uid, MaterialReclaimerComponent? component = null, ActiveMaterialReclaimerComponent? active = null)
  125. {
  126. if (!Resolve(uid, ref component, ref active, false))
  127. return false;
  128. RemCompDeferred(uid, active);
  129. return true;
  130. }
  131. /// <summary>
  132. /// Spawns the materials and chemicals associated
  133. /// with an entity. Also deletes the item.
  134. /// </summary>
  135. public virtual void Reclaim(EntityUid uid,
  136. EntityUid item,
  137. float completion = 1f,
  138. MaterialReclaimerComponent? component = null)
  139. {
  140. if (!Resolve(uid, ref component))
  141. return;
  142. component.ItemsProcessed++;
  143. if (component.CutOffSound)
  144. {
  145. _audio.Stop(component.Stream);
  146. }
  147. Dirty(uid, component);
  148. }
  149. /// <summary>
  150. /// Sets the Enabled field on the reclaimer.
  151. /// </summary>
  152. public bool SetReclaimerEnabled(EntityUid uid, bool enabled, MaterialReclaimerComponent? component = null)
  153. {
  154. if (!Resolve(uid, ref component, false))
  155. return true;
  156. if (component.Broken && enabled)
  157. return false;
  158. component.Enabled = enabled;
  159. AmbientSound.SetAmbience(uid, enabled && component.Powered);
  160. Dirty(uid, component);
  161. return true;
  162. }
  163. /// <summary>
  164. /// Whether or not the specified reclaimer can currently
  165. /// begin reclaiming another entity.
  166. /// </summary>
  167. public bool CanStart(EntityUid uid, MaterialReclaimerComponent component)
  168. {
  169. if (HasComp<ActiveMaterialReclaimerComponent>(uid))
  170. return false;
  171. return component.Powered && component.Enabled && !component.Broken;
  172. }
  173. /// <summary>
  174. /// Whether or not the reclaimer satisfies the conditions
  175. /// allowing it to gib/reclaim a living creature.
  176. /// </summary>
  177. public bool CanGib(EntityUid uid, EntityUid victim, MaterialReclaimerComponent component)
  178. {
  179. return component.Powered &&
  180. component.Enabled &&
  181. !component.Broken &&
  182. HasComp<BodyComponent>(victim) &&
  183. _emag.CheckFlag(uid, EmagType.Interaction);
  184. }
  185. /// <summary>
  186. /// Gets the duration of processing a specified entity.
  187. /// Processing is calculated from the sum of the materials within the entity.
  188. /// It does not regard the chemicals within it.
  189. /// </summary>
  190. public TimeSpan GetReclaimingDuration(EntityUid reclaimer,
  191. EntityUid item,
  192. MaterialReclaimerComponent? reclaimerComponent = null,
  193. PhysicalCompositionComponent? compositionComponent = null)
  194. {
  195. if (!Resolve(reclaimer, ref reclaimerComponent))
  196. return TimeSpan.Zero;
  197. if (!reclaimerComponent.ScaleProcessSpeed ||
  198. !Resolve(item, ref compositionComponent, false))
  199. return reclaimerComponent.MinimumProcessDuration;
  200. var materialSum = compositionComponent.MaterialComposition.Values.Sum();
  201. materialSum *= CompOrNull<StackComponent>(item)?.Count ?? 1;
  202. var duration = TimeSpan.FromSeconds(materialSum / reclaimerComponent.MaterialProcessRate);
  203. if (duration < reclaimerComponent.MinimumProcessDuration)
  204. duration = reclaimerComponent.MinimumProcessDuration;
  205. return duration;
  206. }
  207. /// <inheritdoc/>
  208. public override void Update(float frameTime)
  209. {
  210. base.Update(frameTime);
  211. var query = EntityQueryEnumerator<ActiveMaterialReclaimerComponent, MaterialReclaimerComponent>();
  212. while (query.MoveNext(out var uid, out var active, out var reclaimer))
  213. {
  214. if (Timing.CurTime < active.EndTime)
  215. continue;
  216. TryFinishProcessItem(uid, reclaimer, active);
  217. }
  218. }
  219. }
  220. [ByRefEvent]
  221. public record struct GotReclaimedEvent(EntityCoordinates ReclaimerCoordinates);