SharedGrapplingGunSystem.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. using System.Numerics;
  2. using Content.Shared.CombatMode;
  3. using Content.Shared.Hands;
  4. using Content.Shared.Hands.Components;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Movement.Events;
  7. using Content.Shared.Physics;
  8. using Content.Shared.Projectiles;
  9. using Content.Shared.Weapons.Ranged.Components;
  10. using Content.Shared.Weapons.Ranged.Systems;
  11. using Robust.Shared.Audio.Systems;
  12. using Robust.Shared.Network;
  13. using Robust.Shared.Physics;
  14. using Robust.Shared.Physics.Components;
  15. using Robust.Shared.Physics.Dynamics.Joints;
  16. using Robust.Shared.Physics.Systems;
  17. using Robust.Shared.Serialization;
  18. using Robust.Shared.Timing;
  19. namespace Content.Shared.Weapons.Misc;
  20. public abstract class SharedGrapplingGunSystem : EntitySystem
  21. {
  22. [Dependency] protected readonly IGameTiming Timing = default!;
  23. [Dependency] private readonly INetManager _netManager = default!;
  24. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  25. [Dependency] private readonly SharedAudioSystem _audio = default!;
  26. [Dependency] private readonly SharedJointSystem _joints = default!;
  27. [Dependency] private readonly SharedGunSystem _gun = default!;
  28. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  29. public const string GrapplingJoint = "grappling";
  30. public override void Initialize()
  31. {
  32. base.Initialize();
  33. SubscribeLocalEvent<GrapplingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide);
  34. SubscribeLocalEvent<GrapplingProjectileComponent, JointRemovedEvent>(OnGrappleJointRemoved);
  35. SubscribeLocalEvent<CanWeightlessMoveEvent>(OnWeightlessMove);
  36. SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel);
  37. SubscribeLocalEvent<GrapplingGunComponent, GunShotEvent>(OnGrapplingShot);
  38. SubscribeLocalEvent<GrapplingGunComponent, ActivateInWorldEvent>(OnGunActivate);
  39. SubscribeLocalEvent<GrapplingGunComponent, HandDeselectedEvent>(OnGrapplingDeselected);
  40. }
  41. private void OnGrappleJointRemoved(EntityUid uid, GrapplingProjectileComponent component, JointRemovedEvent args)
  42. {
  43. if (_netManager.IsServer)
  44. QueueDel(uid);
  45. }
  46. private void OnGrapplingShot(EntityUid uid, GrapplingGunComponent component, ref GunShotEvent args)
  47. {
  48. foreach (var (shotUid, _) in args.Ammo)
  49. {
  50. if (!HasComp<GrapplingProjectileComponent>(shotUid))
  51. continue;
  52. //todo: this doesn't actually support multigrapple
  53. // At least show the visuals.
  54. component.Projectile = shotUid.Value;
  55. Dirty(uid, component);
  56. var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value);
  57. visuals.Sprite = component.RopeSprite;
  58. visuals.OffsetA = new Vector2(0f, 0.5f);
  59. visuals.Target = GetNetEntity(uid);
  60. Dirty(shotUid.Value, visuals);
  61. }
  62. TryComp<AppearanceComponent>(uid, out var appearance);
  63. _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, false, appearance);
  64. Dirty(uid, component);
  65. }
  66. private void OnGrapplingDeselected(EntityUid uid, GrapplingGunComponent component, HandDeselectedEvent args)
  67. {
  68. SetReeling(uid, component, false, args.User);
  69. }
  70. private void OnGrapplingReel(RequestGrapplingReelMessage msg, EntitySessionEventArgs args)
  71. {
  72. var player = args.SenderSession.AttachedEntity;
  73. if (!TryComp<HandsComponent>(player, out var hands) ||
  74. !TryComp<GrapplingGunComponent>(hands.ActiveHandEntity, out var grappling))
  75. {
  76. return;
  77. }
  78. if (msg.Reeling &&
  79. (!TryComp<CombatModeComponent>(player, out var combatMode) ||
  80. !combatMode.IsInCombatMode))
  81. {
  82. return;
  83. }
  84. SetReeling(hands.ActiveHandEntity.Value, grappling, msg.Reeling, player.Value);
  85. }
  86. private void OnWeightlessMove(ref CanWeightlessMoveEvent ev)
  87. {
  88. if (ev.CanMove || !TryComp<JointRelayTargetComponent>(ev.Uid, out var relayComp))
  89. return;
  90. foreach (var relay in relayComp.Relayed)
  91. {
  92. if (TryComp<JointComponent>(relay, out var jointRelay) && jointRelay.GetJoints.ContainsKey(GrapplingJoint))
  93. {
  94. ev.CanMove = true;
  95. return;
  96. }
  97. }
  98. }
  99. private void OnGunActivate(EntityUid uid, GrapplingGunComponent component, ActivateInWorldEvent args)
  100. {
  101. if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex || component.Projectile is not {} projectile)
  102. return;
  103. _audio.PlayPredicted(component.CycleSound, uid, args.User);
  104. _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, true);
  105. if (_netManager.IsServer)
  106. QueueDel(projectile);
  107. component.Projectile = null;
  108. SetReeling(uid, component, false, args.User);
  109. _gun.ChangeBasicEntityAmmoCount(uid, 1);
  110. args.Handled = true;
  111. }
  112. private void SetReeling(EntityUid uid, GrapplingGunComponent component, bool value, EntityUid? user)
  113. {
  114. if (component.Reeling == value)
  115. return;
  116. if (value)
  117. {
  118. if (Timing.IsFirstTimePredicted)
  119. component.Stream = _audio.PlayPredicted(component.ReelSound, uid, user)?.Entity;
  120. }
  121. else
  122. {
  123. if (Timing.IsFirstTimePredicted)
  124. {
  125. component.Stream = _audio.Stop(component.Stream);
  126. }
  127. }
  128. component.Reeling = value;
  129. Dirty(uid, component);
  130. }
  131. public override void Update(float frameTime)
  132. {
  133. base.Update(frameTime);
  134. var query = EntityQueryEnumerator<GrapplingGunComponent>();
  135. while (query.MoveNext(out var uid, out var grappling))
  136. {
  137. if (!grappling.Reeling)
  138. {
  139. if (Timing.IsFirstTimePredicted)
  140. {
  141. // Just in case.
  142. grappling.Stream = _audio.Stop(grappling.Stream);
  143. }
  144. continue;
  145. }
  146. if (!TryComp<JointComponent>(uid, out var jointComp) ||
  147. !jointComp.GetJoints.TryGetValue(GrapplingJoint, out var joint) ||
  148. joint is not DistanceJoint distance)
  149. {
  150. SetReeling(uid, grappling, false, null);
  151. continue;
  152. }
  153. // TODO: This should be on engine.
  154. distance.MaxLength = MathF.Max(distance.MinLength, distance.MaxLength - grappling.ReelRate * frameTime);
  155. distance.Length = MathF.Min(distance.MaxLength, distance.Length);
  156. _physics.WakeBody(joint.BodyAUid);
  157. _physics.WakeBody(joint.BodyBUid);
  158. if (jointComp.Relay != null)
  159. {
  160. _physics.WakeBody(jointComp.Relay.Value);
  161. }
  162. Dirty(uid, jointComp);
  163. if (distance.MaxLength.Equals(distance.MinLength))
  164. {
  165. SetReeling(uid, grappling, false, null);
  166. }
  167. }
  168. }
  169. private void OnGrappleCollide(EntityUid uid, GrapplingProjectileComponent component, ref ProjectileEmbedEvent args)
  170. {
  171. if (!Timing.IsFirstTimePredicted)
  172. return;
  173. var jointComp = EnsureComp<JointComponent>(uid);
  174. var joint = _joints.CreateDistanceJoint(uid, args.Weapon, anchorA: new Vector2(0f, 0.5f), id: GrapplingJoint);
  175. joint.MaxLength = joint.Length + 0.2f;
  176. joint.Stiffness = 1f;
  177. joint.MinLength = 0.35f;
  178. // Setting velocity directly for mob movement fucks this so need to make them aware of it.
  179. // joint.Breakpoint = 4000f;
  180. Dirty(uid, jointComp);
  181. }
  182. [Serializable, NetSerializable]
  183. protected sealed class RequestGrapplingReelMessage : EntityEventArgs
  184. {
  185. public bool Reeling;
  186. public RequestGrapplingReelMessage(bool reeling)
  187. {
  188. Reeling = reeling;
  189. }
  190. }
  191. }