SharedProjectileSystem.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. using System.Numerics;
  2. using Content.Shared.CombatMode.Pacification;
  3. using Content.Shared.Damage;
  4. using Content.Shared.DoAfter;
  5. using Content.Shared.Hands.EntitySystems;
  6. using Content.Shared.Interaction;
  7. using Content.Shared.Mobs.Components;
  8. using Content.Shared.Throwing;
  9. using Robust.Shared.Audio.Systems;
  10. using Robust.Shared.Map;
  11. using Robust.Shared.Network;
  12. using Robust.Shared.Physics;
  13. using Robust.Shared.Physics.Components;
  14. using Robust.Shared.Physics.Dynamics;
  15. using Robust.Shared.Physics.Events;
  16. using Robust.Shared.Physics.Systems;
  17. using Robust.Shared.Serialization;
  18. using Robust.Shared.Utility;
  19. namespace Content.Shared.Projectiles;
  20. public abstract partial class SharedProjectileSystem : EntitySystem
  21. {
  22. public const string ProjectileFixture = "projectile";
  23. [Dependency] private readonly INetManager _net = default!;
  24. [Dependency] private readonly SharedAudioSystem _audio = default!;
  25. [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
  26. [Dependency] private readonly SharedHandsSystem _hands = default!;
  27. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  28. [Dependency] private readonly SharedTransformSystem _transform = default!;
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
  33. SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
  34. SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
  35. SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
  36. SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
  37. SubscribeLocalEvent<EmbeddedContainerComponent, EntityTerminatingEvent>(OnEmbeddableTermination);
  38. }
  39. private void OnEmbedActivate(Entity<EmbeddableProjectileComponent> embeddable, ref ActivateInWorldEvent args)
  40. {
  41. // Unremovable embeddables moment
  42. if (embeddable.Comp.RemovalTime == null)
  43. return;
  44. if (args.Handled || !args.Complex || !TryComp<PhysicsComponent>(embeddable, out var physics) ||
  45. physics.BodyType != BodyType.Static)
  46. return;
  47. args.Handled = true;
  48. _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
  49. args.User,
  50. embeddable.Comp.RemovalTime.Value,
  51. new RemoveEmbeddedProjectileEvent(),
  52. eventTarget: embeddable,
  53. target: embeddable));
  54. }
  55. private void OnEmbedRemove(Entity<EmbeddableProjectileComponent> embeddable, ref RemoveEmbeddedProjectileEvent args)
  56. {
  57. // Whacky prediction issues.
  58. if (args.Cancelled || _net.IsClient)
  59. return;
  60. EmbedDetach(embeddable, embeddable.Comp, args.User);
  61. // try place it in the user's hand
  62. _hands.TryPickupAnyHand(args.User, embeddable);
  63. }
  64. private void OnEmbedThrowDoHit(Entity<EmbeddableProjectileComponent> embeddable, ref ThrowDoHitEvent args)
  65. {
  66. if (!embeddable.Comp.EmbedOnThrow)
  67. return;
  68. EmbedAttach(embeddable, args.Target, null, embeddable.Comp);
  69. }
  70. private void OnEmbedProjectileHit(Entity<EmbeddableProjectileComponent> embeddable, ref ProjectileHitEvent args)
  71. {
  72. EmbedAttach(embeddable, args.Target, args.Shooter, embeddable.Comp);
  73. // Raise a specific event for projectiles.
  74. if (TryComp(embeddable, out ProjectileComponent? projectile))
  75. {
  76. var ev = new ProjectileEmbedEvent(projectile.Shooter!.Value, projectile.Weapon!.Value, args.Target);
  77. RaiseLocalEvent(embeddable, ref ev);
  78. }
  79. }
  80. private void EmbedAttach(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component)
  81. {
  82. TryComp<PhysicsComponent>(uid, out var physics);
  83. _physics.SetLinearVelocity(uid, Vector2.Zero, body: physics);
  84. _physics.SetBodyType(uid, BodyType.Static, body: physics);
  85. var xform = Transform(uid);
  86. _transform.SetParent(uid, xform, target);
  87. if (component.Offset != Vector2.Zero)
  88. {
  89. var rotation = xform.LocalRotation;
  90. if (TryComp<ThrowingAngleComponent>(uid, out var throwingAngleComp))
  91. rotation += throwingAngleComp.Angle;
  92. _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), xform);
  93. }
  94. _audio.PlayPredicted(component.Sound, uid, null);
  95. component.EmbeddedIntoUid = target;
  96. var ev = new EmbedEvent(user, target);
  97. RaiseLocalEvent(uid, ref ev);
  98. Dirty(uid, component);
  99. EnsureComp<EmbeddedContainerComponent>(target, out var embeddedContainer);
  100. //Assert that this entity not embed
  101. DebugTools.AssertEqual(embeddedContainer.EmbeddedObjects.Contains(uid), false);
  102. embeddedContainer.EmbeddedObjects.Add(uid);
  103. }
  104. public void EmbedDetach(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null)
  105. {
  106. if (!Resolve(uid, ref component))
  107. return;
  108. if (component.DeleteOnRemove)
  109. {
  110. QueueDel(uid);
  111. return;
  112. }
  113. if (component.EmbeddedIntoUid is not null)
  114. {
  115. if (TryComp<EmbeddedContainerComponent>(component.EmbeddedIntoUid.Value, out var embeddedContainer))
  116. embeddedContainer.EmbeddedObjects.Remove(uid);
  117. }
  118. var xform = Transform(uid);
  119. TryComp<PhysicsComponent>(uid, out var physics);
  120. _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
  121. _transform.AttachToGridOrMap(uid, xform);
  122. component.EmbeddedIntoUid = null;
  123. Dirty(uid, component);
  124. // Reset whether the projectile has damaged anything if it successfully was removed
  125. if (TryComp<ProjectileComponent>(uid, out var projectile))
  126. {
  127. projectile.Shooter = null;
  128. projectile.Weapon = null;
  129. projectile.ProjectileSpent = false;
  130. Dirty(uid, projectile);
  131. }
  132. if (user != null)
  133. {
  134. // Land it just coz uhhh yeah
  135. var landEv = new LandEvent(user, true);
  136. RaiseLocalEvent(uid, ref landEv);
  137. }
  138. _physics.WakeBody(uid, body: physics);
  139. }
  140. private void OnEmbeddableTermination(Entity<EmbeddedContainerComponent> container, ref EntityTerminatingEvent args)
  141. {
  142. DetachAllEmbedded(container);
  143. }
  144. public void DetachAllEmbedded(Entity<EmbeddedContainerComponent> container)
  145. {
  146. foreach (var embedded in container.Comp.EmbeddedObjects)
  147. {
  148. if (!TryComp<EmbeddableProjectileComponent>(embedded, out var embeddedComp))
  149. continue;
  150. EmbedDetach(embedded, embeddedComp);
  151. }
  152. }
  153. private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
  154. {
  155. if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
  156. {
  157. args.Cancelled = true;
  158. }
  159. }
  160. public void SetShooter(EntityUid id, ProjectileComponent component, EntityUid shooterId)
  161. {
  162. if (component.Shooter == shooterId)
  163. return;
  164. component.Shooter = shooterId;
  165. Dirty(id, component);
  166. }
  167. [Serializable, NetSerializable]
  168. private sealed partial class RemoveEmbeddedProjectileEvent : DoAfterEvent
  169. {
  170. public override DoAfterEvent Clone() => this;
  171. }
  172. }
  173. [Serializable, NetSerializable]
  174. public sealed class ImpactEffectEvent : EntityEventArgs
  175. {
  176. public string Prototype;
  177. public NetCoordinates Coordinates;
  178. public ImpactEffectEvent(string prototype, NetCoordinates coordinates)
  179. {
  180. Prototype = prototype;
  181. Coordinates = coordinates;
  182. }
  183. }
  184. /// <summary>
  185. /// Raised when an entity is just about to be hit with a projectile but can reflect it
  186. /// </summary>
  187. [ByRefEvent]
  188. public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled);
  189. /// <summary>
  190. /// Raised when a projectile hits an entity
  191. /// </summary>
  192. [ByRefEvent]
  193. public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, EntityUid? Shooter = null);