PayloadSystem.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Explosion.EntitySystems;
  3. using Content.Shared.Chemistry.Components;
  4. using Content.Shared.Database;
  5. using Content.Shared.Examine;
  6. using Content.Shared.Payload.Components;
  7. using Content.Shared.Tag;
  8. using Content.Shared.Chemistry.EntitySystems;
  9. using Robust.Shared.Containers;
  10. using Robust.Shared.Serialization.Manager;
  11. using Robust.Shared.Utility;
  12. using System.Linq;
  13. using Robust.Server.GameObjects;
  14. namespace Content.Server.Payload.EntitySystems;
  15. public sealed class PayloadSystem : EntitySystem
  16. {
  17. [Dependency] private readonly TagSystem _tagSystem = default!;
  18. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  19. [Dependency] private readonly TransformSystem _transform = default!;
  20. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  21. [Dependency] private readonly IComponentFactory _componentFactory = default!;
  22. [Dependency] private readonly ISerializationManager _serializationManager = default!;
  23. public override void Initialize()
  24. {
  25. base.Initialize();
  26. SubscribeLocalEvent<PayloadCaseComponent, TriggerEvent>(OnCaseTriggered);
  27. SubscribeLocalEvent<PayloadTriggerComponent, TriggerEvent>(OnTriggerTriggered);
  28. SubscribeLocalEvent<PayloadCaseComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
  29. SubscribeLocalEvent<PayloadCaseComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
  30. SubscribeLocalEvent<PayloadCaseComponent, ExaminedEvent>(OnExamined);
  31. SubscribeLocalEvent<ChemicalPayloadComponent, TriggerEvent>(HandleChemicalPayloadTrigger);
  32. }
  33. public IEnumerable<EntityUid> GetAllPayloads(EntityUid uid, ContainerManagerComponent? contMan = null)
  34. {
  35. if (!Resolve(uid, ref contMan, false))
  36. yield break;
  37. foreach (var container in contMan.Containers.Values)
  38. {
  39. foreach (var entity in container.ContainedEntities)
  40. {
  41. if (_tagSystem.HasTag(entity, "Payload"))
  42. yield return entity;
  43. }
  44. }
  45. }
  46. private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args)
  47. {
  48. if (!TryComp(uid, out ContainerManagerComponent? contMan))
  49. return;
  50. // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs.
  51. foreach (var ent in GetAllPayloads(uid, contMan))
  52. {
  53. RaiseLocalEvent(ent, args, false);
  54. }
  55. }
  56. private void OnTriggerTriggered(EntityUid uid, PayloadTriggerComponent component, TriggerEvent args)
  57. {
  58. if (!component.Active)
  59. return;
  60. if (Transform(uid).ParentUid is not { Valid: true } parent)
  61. return;
  62. // Ensure we don't enter a trigger-loop
  63. DebugTools.Assert(!_tagSystem.HasTag(uid, "Payload"));
  64. RaiseLocalEvent(parent, args, false);
  65. }
  66. private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args)
  67. {
  68. if (!TryComp(args.Entity, out PayloadTriggerComponent? trigger))
  69. return;
  70. trigger.Active = true;
  71. if (trigger.Components == null)
  72. return;
  73. // ANY payload trigger that gets inserted can grant components. It is up to the construction graphs to determine trigger capacity.
  74. foreach (var (name, data) in trigger.Components)
  75. {
  76. if (!_componentFactory.TryGetRegistration(name, out var registration))
  77. continue;
  78. if (HasComp(uid, registration.Type))
  79. continue;
  80. if (_componentFactory.GetComponent(registration.Type) is not Component component)
  81. continue;
  82. var temp = (object) component;
  83. _serializationManager.CopyTo(data.Component, ref temp);
  84. EntityManager.AddComponent(uid, (Component) temp!);
  85. trigger.GrantedComponents.Add(registration.Type);
  86. }
  87. }
  88. private void OnEntityRemoved(EntityUid uid, PayloadCaseComponent component, EntRemovedFromContainerMessage args)
  89. {
  90. if (!TryComp(args.Entity, out PayloadTriggerComponent? trigger))
  91. return;
  92. trigger.Active = false;
  93. foreach (var type in trigger.GrantedComponents)
  94. {
  95. EntityManager.RemoveComponent(uid, type);
  96. }
  97. trigger.GrantedComponents.Clear();
  98. }
  99. private void OnExamined(EntityUid uid, PayloadCaseComponent component, ExaminedEvent args)
  100. {
  101. using (args.PushGroup(nameof(PayloadCaseComponent)))
  102. {
  103. if (!args.IsInDetailsRange)
  104. {
  105. args.PushMarkup(Loc.GetString("payload-case-not-close-enough", ("ent", uid)));
  106. return;
  107. }
  108. if (GetAllPayloads(uid).Any())
  109. {
  110. args.PushMarkup(Loc.GetString("payload-case-has-payload", ("ent", uid)));
  111. }
  112. else
  113. {
  114. args.PushMarkup(Loc.GetString("payload-case-does-not-have-payload", ("ent", uid)));
  115. }
  116. }
  117. }
  118. private void HandleChemicalPayloadTrigger(Entity<ChemicalPayloadComponent> entity, ref TriggerEvent args)
  119. {
  120. if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA
  121. || entity.Comp.BeakerSlotB.Item is not EntityUid beakerB
  122. || !TryComp(beakerA, out FitsInDispenserComponent? compA)
  123. || !TryComp(beakerB, out FitsInDispenserComponent? compB)
  124. || !_solutionContainerSystem.TryGetSolution(beakerA, compA.Solution, out var solnA, out var solutionA)
  125. || !_solutionContainerSystem.TryGetSolution(beakerB, compB.Solution, out var solnB, out var solutionB)
  126. || solutionA.Volume == 0
  127. || solutionB.Volume == 0)
  128. {
  129. return;
  130. }
  131. var solStringA = SharedSolutionContainerSystem.ToPrettyString(solutionA);
  132. var solStringB = SharedSolutionContainerSystem.ToPrettyString(solutionB);
  133. _adminLogger.Add(LogType.ChemicalReaction,
  134. $"Chemical bomb payload {ToPrettyString(entity.Owner):payload} at {_transform.GetMapCoordinates(entity.Owner):location} is combining two solutions: {solStringA:solutionA} and {solStringB:solutionB}");
  135. solutionA.MaxVolume += solutionB.MaxVolume;
  136. _solutionContainerSystem.TryAddSolution(solnA.Value, solutionB);
  137. _solutionContainerSystem.RemoveAllSolution(solnB.Value);
  138. // The grenade might be a dud. Redistribute solution:
  139. var tmpSol = _solutionContainerSystem.SplitSolution(solnA.Value, solutionA.Volume * solutionB.MaxVolume / solutionA.MaxVolume);
  140. _solutionContainerSystem.TryAddSolution(solnB.Value, tmpSol);
  141. solutionA.MaxVolume -= solutionB.MaxVolume;
  142. _solutionContainerSystem.UpdateChemicals(solnA.Value);
  143. args.Handled = true;
  144. }
  145. }