1
0

SolutionInjectOnEventSystem.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. using Content.Server.Body.Components;
  2. using Content.Server.Body.Systems;
  3. using Content.Server.Chemistry.Components;
  4. using Content.Shared.Chemistry.EntitySystems;
  5. using Content.Shared.Chemistry.Events;
  6. using Content.Shared.Inventory;
  7. using Content.Shared.Popups;
  8. using Content.Shared.Projectiles;
  9. using Content.Shared.Tag;
  10. using Content.Shared.Weapons.Melee.Events;
  11. using Robust.Shared.Collections;
  12. namespace Content.Server.Chemistry.EntitySystems;
  13. /// <summary>
  14. /// System for handling the different inheritors of <see cref="BaseSolutionInjectOnEventComponent"/>.
  15. /// Subscribes to relevent events and performs solution injections when they are raised.
  16. /// </summary>
  17. public sealed class SolutionInjectOnCollideSystem : EntitySystem
  18. {
  19. [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
  20. [Dependency] private readonly InventorySystem _inventory = default!;
  21. [Dependency] private readonly SharedPopupSystem _popup = default!;
  22. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
  23. [Dependency] private readonly TagSystem _tag = default!;
  24. public override void Initialize()
  25. {
  26. base.Initialize();
  27. SubscribeLocalEvent<SolutionInjectOnProjectileHitComponent, ProjectileHitEvent>(HandleProjectileHit);
  28. SubscribeLocalEvent<SolutionInjectOnEmbedComponent, EmbedEvent>(HandleEmbed);
  29. SubscribeLocalEvent<MeleeChemicalInjectorComponent, MeleeHitEvent>(HandleMeleeHit);
  30. SubscribeLocalEvent<SolutionInjectWhileEmbeddedComponent, InjectOverTimeEvent>(OnInjectOverTime);
  31. }
  32. private void HandleProjectileHit(Entity<SolutionInjectOnProjectileHitComponent> entity, ref ProjectileHitEvent args)
  33. {
  34. DoInjection((entity.Owner, entity.Comp), args.Target, args.Shooter);
  35. }
  36. private void HandleEmbed(Entity<SolutionInjectOnEmbedComponent> entity, ref EmbedEvent args)
  37. {
  38. DoInjection((entity.Owner, entity.Comp), args.Embedded, args.Shooter);
  39. }
  40. private void HandleMeleeHit(Entity<MeleeChemicalInjectorComponent> entity, ref MeleeHitEvent args)
  41. {
  42. // MeleeHitEvent is weird, so we have to filter to make sure we actually
  43. // hit something and aren't just examining the weapon.
  44. if (args.IsHit)
  45. TryInjectTargets((entity.Owner, entity.Comp), args.HitEntities, args.User);
  46. }
  47. private void OnInjectOverTime(Entity<SolutionInjectWhileEmbeddedComponent> entity, ref InjectOverTimeEvent args)
  48. {
  49. DoInjection((entity.Owner, entity.Comp), args.EmbeddedIntoUid);
  50. }
  51. private void DoInjection(Entity<BaseSolutionInjectOnEventComponent> injectorEntity, EntityUid target, EntityUid? source = null)
  52. {
  53. TryInjectTargets(injectorEntity, [target], source);
  54. }
  55. /// <summary>
  56. /// Filters <paramref name="targets"/> for valid targets and tries to inject a portion of <see cref="BaseSolutionInjectOnEventComponent.Solution"/> into
  57. /// each valid target's bloodstream.
  58. /// </summary>
  59. /// <remarks>
  60. /// Targets are invalid if any of the following are true:
  61. /// <list type="bullet">
  62. /// <item>The target does not have a bloodstream.</item>
  63. /// <item><see cref="BaseSolutionInjectOnEventComponent.PierceArmor"/> is false and the target is wearing a hardsuit.</item>
  64. /// <item><see cref="BaseSolutionInjectOnEventComponent.BlockSlots"/> is not NONE and the target has an item equipped in any of the specified slots.</item>
  65. /// </list>
  66. /// </remarks>
  67. /// <returns>true if at least one target was successfully injected, otherwise false</returns>
  68. private bool TryInjectTargets(Entity<BaseSolutionInjectOnEventComponent> injector, IReadOnlyList<EntityUid> targets, EntityUid? source = null)
  69. {
  70. // Make sure we have at least one target
  71. if (targets.Count == 0)
  72. return false;
  73. // Get the solution to inject
  74. if (!_solutionContainer.TryGetSolution(injector.Owner, injector.Comp.Solution, out var injectorSolution))
  75. return false;
  76. // Build a list of bloodstreams to inject into
  77. var targetBloodstreams = new ValueList<Entity<BloodstreamComponent>>();
  78. foreach (var target in targets)
  79. {
  80. if (Deleted(target))
  81. continue;
  82. // Yuck, this is way to hardcodey for my tastes
  83. // TODO blocking injection with a hardsuit should probably done with a cancellable event or something
  84. if (!injector.Comp.PierceArmor && _inventory.TryGetSlotEntity(target, "outerClothing", out var suit) && _tag.HasTag(suit.Value, "Hardsuit"))
  85. {
  86. // Only show popup to attacker
  87. if (source != null)
  88. _popup.PopupEntity(Loc.GetString(injector.Comp.BlockedByHardsuitPopupMessage, ("weapon", injector.Owner), ("target", target)), target, source.Value, PopupType.SmallCaution);
  89. continue;
  90. }
  91. // Check if the target has anything equipped in a slot that would block injection
  92. if (injector.Comp.BlockSlots != SlotFlags.NONE)
  93. {
  94. var blocked = false;
  95. var containerEnumerator = _inventory.GetSlotEnumerator(target, injector.Comp.BlockSlots);
  96. while (containerEnumerator.MoveNext(out var container))
  97. {
  98. if (container.ContainedEntity != null)
  99. {
  100. blocked = true;
  101. break;
  102. }
  103. }
  104. if (blocked)
  105. continue;
  106. }
  107. // Make sure the target has a bloodstream
  108. if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
  109. continue;
  110. // Checks passed; add this target's bloodstream to the list
  111. targetBloodstreams.Add((target, bloodstream));
  112. }
  113. // Make sure we got at least one bloodstream
  114. if (targetBloodstreams.Count == 0)
  115. return false;
  116. // Extract total needed solution from the injector
  117. var removedSolution = _solutionContainer.SplitSolution(injectorSolution.Value, injector.Comp.TransferAmount * targetBloodstreams.Count);
  118. // Adjust solution amount based on transfer efficiency
  119. var solutionToInject = removedSolution.SplitSolution(removedSolution.Volume * injector.Comp.TransferEfficiency);
  120. // Calculate how much of the adjusted solution each target will get
  121. var volumePerBloodstream = solutionToInject.Volume * (1f / targetBloodstreams.Count);
  122. var anySuccess = false;
  123. foreach (var targetBloodstream in targetBloodstreams)
  124. {
  125. // Take our portion of the adjusted solution for this target
  126. var individualInjection = solutionToInject.SplitSolution(volumePerBloodstream);
  127. // Inject our portion into the target's bloodstream
  128. if (_bloodstream.TryAddToChemicals(targetBloodstream.Owner, individualInjection, targetBloodstream.Comp))
  129. anySuccess = true;
  130. }
  131. // Huzzah!
  132. return anySuccess;
  133. }
  134. }