1
0

SharedTetherGunSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. using System.Diagnostics.CodeAnalysis;
  2. using Content.Shared.ActionBlocker;
  3. using Content.Shared.Buckle.Components;
  4. using Content.Shared.Hands.Components;
  5. using Content.Shared.Interaction;
  6. using Content.Shared.Mobs.Systems;
  7. using Content.Shared.Movement.Events;
  8. using Content.Shared.Throwing;
  9. using Content.Shared.Toggleable;
  10. using Robust.Shared.Audio;
  11. using Robust.Shared.Audio.Systems;
  12. using Robust.Shared.Containers;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Network;
  15. using Robust.Shared.Physics;
  16. using Robust.Shared.Physics.Components;
  17. using Robust.Shared.Physics.Systems;
  18. using Robust.Shared.Serialization;
  19. namespace Content.Shared.Weapons.Misc;
  20. public abstract partial class SharedTetherGunSystem : EntitySystem
  21. {
  22. [Dependency] private readonly INetManager _netManager = default!;
  23. [Dependency] private readonly ActionBlockerSystem _blocker = default!;
  24. [Dependency] private readonly MobStateSystem _mob = default!;
  25. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  26. [Dependency] private readonly SharedAudioSystem _audio = default!;
  27. [Dependency] private readonly SharedContainerSystem _container = default!;
  28. [Dependency] private readonly SharedJointSystem _joints = default!;
  29. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  30. [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
  31. [Dependency] private readonly ThrowingSystem _throwing = default!;
  32. [Dependency] private readonly ThrownItemSystem _thrown = default!;
  33. private const string TetherJoint = "tether";
  34. private const float SpinVelocity = MathF.PI;
  35. private const float AngularChange = 1f;
  36. public override void Initialize()
  37. {
  38. base.Initialize();
  39. SubscribeLocalEvent<TetherGunComponent, ActivateInWorldEvent>(OnTetherActivate);
  40. SubscribeLocalEvent<TetherGunComponent, AfterInteractEvent>(OnTetherRanged);
  41. SubscribeAllEvent<RequestTetherMoveEvent>(OnTetherMove);
  42. SubscribeLocalEvent<TetheredComponent, BuckleAttemptEvent>(OnTetheredBuckleAttempt);
  43. SubscribeLocalEvent<TetheredComponent, UpdateCanMoveEvent>(OnTetheredUpdateCanMove);
  44. SubscribeLocalEvent<TetheredComponent, EntGotInsertedIntoContainerMessage>(OnTetheredContainerInserted);
  45. InitializeForce();
  46. }
  47. private void OnTetheredContainerInserted(EntityUid uid, TetheredComponent component, EntGotInsertedIntoContainerMessage args)
  48. {
  49. if (TryComp<TetherGunComponent>(component.Tetherer, out var tetherGun))
  50. {
  51. StopTether(component.Tetherer, tetherGun);
  52. return;
  53. }
  54. if (TryComp<ForceGunComponent>(component.Tetherer, out var forceGun))
  55. {
  56. StopTether(component.Tetherer, forceGun);
  57. return;
  58. }
  59. }
  60. private void OnTetheredBuckleAttempt(EntityUid uid, TetheredComponent component, ref BuckleAttemptEvent args)
  61. {
  62. args.Cancelled = true;
  63. }
  64. private void OnTetheredUpdateCanMove(EntityUid uid, TetheredComponent component, UpdateCanMoveEvent args)
  65. {
  66. args.Cancel();
  67. }
  68. public override void Update(float frameTime)
  69. {
  70. base.Update(frameTime);
  71. // Just to set the angular velocity due to joint funnies
  72. var tetheredQuery = EntityQueryEnumerator<TetheredComponent, PhysicsComponent>();
  73. while (tetheredQuery.MoveNext(out var uid, out _, out var physics))
  74. {
  75. var sign = Math.Sign(physics.AngularVelocity);
  76. if (sign == 0)
  77. {
  78. sign = 1;
  79. }
  80. var targetVelocity = MathF.PI * sign;
  81. var shortFall = Math.Clamp(targetVelocity - physics.AngularVelocity, -SpinVelocity, SpinVelocity);
  82. shortFall *= frameTime * AngularChange;
  83. _physics.ApplyAngularImpulse(uid, shortFall, body: physics);
  84. }
  85. }
  86. private void OnTetherMove(RequestTetherMoveEvent msg, EntitySessionEventArgs args)
  87. {
  88. var user = args.SenderSession.AttachedEntity;
  89. if (user == null)
  90. return;
  91. if (!TryGetTetherGun(user.Value, out var gunUid, out var gun) || gun.TetherEntity == null)
  92. {
  93. return;
  94. }
  95. var coords = GetCoordinates(msg.Coordinates);
  96. if (!coords.TryDistance(EntityManager, TransformSystem, Transform(gunUid.Value).Coordinates,
  97. out var distance) ||
  98. distance > gun.MaxDistance)
  99. {
  100. return;
  101. }
  102. TransformSystem.SetCoordinates(gun.TetherEntity.Value, coords);
  103. }
  104. private void OnTetherRanged(EntityUid uid, TetherGunComponent component, AfterInteractEvent args)
  105. {
  106. if (args.Target == null || args.Handled)
  107. return;
  108. TryTether(uid, args.Target.Value, args.User, component);
  109. }
  110. protected bool TryGetTetherGun(EntityUid user, [NotNullWhen(true)] out EntityUid? gunUid, [NotNullWhen(true)] out TetherGunComponent? gun)
  111. {
  112. gunUid = null;
  113. gun = null;
  114. if (!TryComp<HandsComponent>(user, out var hands) ||
  115. !TryComp(hands.ActiveHandEntity, out gun) ||
  116. _container.IsEntityInContainer(user))
  117. {
  118. return false;
  119. }
  120. gunUid = hands.ActiveHandEntity.Value;
  121. return true;
  122. }
  123. private void OnTetherActivate(EntityUid uid, TetherGunComponent component, ActivateInWorldEvent args)
  124. {
  125. if (!args.Complex)
  126. return;
  127. StopTether(uid, component);
  128. }
  129. public bool TryTether(EntityUid gun, EntityUid target, EntityUid? user, BaseForceGunComponent? component = null)
  130. {
  131. if (!Resolve(gun, ref component))
  132. return false;
  133. if (!CanTether(gun, component, target, user))
  134. return false;
  135. StartTether(gun, component, target, user);
  136. return true;
  137. }
  138. protected virtual bool CanTether(EntityUid uid, BaseForceGunComponent component, EntityUid target, EntityUid? user)
  139. {
  140. if (HasComp<TetheredComponent>(target) || !TryComp<PhysicsComponent>(target, out var physics))
  141. return false;
  142. if (physics.BodyType == BodyType.Static && !component.CanUnanchor ||
  143. _container.IsEntityInContainer(target))
  144. return false;
  145. if (physics.Mass > component.MassLimit)
  146. return false;
  147. if (!component.CanTetherAlive && _mob.IsAlive(target))
  148. return false;
  149. if (TryComp<StrapComponent>(target, out var strap) && strap.BuckledEntities.Count > 0)
  150. return false;
  151. return true;
  152. }
  153. protected virtual void StartTether(EntityUid gunUid, BaseForceGunComponent component, EntityUid target, EntityUid? user,
  154. PhysicsComponent? targetPhysics = null, TransformComponent? targetXform = null)
  155. {
  156. if (!Resolve(target, ref targetPhysics, ref targetXform))
  157. return;
  158. if (component.Tethered != null)
  159. {
  160. StopTether(gunUid, component, true);
  161. }
  162. TryComp<AppearanceComponent>(gunUid, out var appearance);
  163. _appearance.SetData(gunUid, TetherVisualsStatus.Key, true, appearance);
  164. _appearance.SetData(gunUid, ToggleableLightVisuals.Enabled, true, appearance);
  165. // Target updates
  166. TransformSystem.Unanchor(target, targetXform);
  167. component.Tethered = target;
  168. var tethered = EnsureComp<TetheredComponent>(target);
  169. _physics.SetBodyStatus(target, targetPhysics, BodyStatus.InAir, false);
  170. _physics.SetSleepingAllowed(target, targetPhysics, false);
  171. tethered.Tetherer = gunUid;
  172. tethered.OriginalAngularDamping = targetPhysics.AngularDamping;
  173. _physics.SetAngularDamping(target, targetPhysics, 0f);
  174. _physics.SetLinearDamping(target, targetPhysics, 0f);
  175. _physics.SetAngularVelocity(target, SpinVelocity, body: targetPhysics);
  176. _physics.WakeBody(target, body: targetPhysics);
  177. var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
  178. thrown.Thrower = gunUid;
  179. _blocker.UpdateCanMove(target);
  180. // Invisible tether entity
  181. var tether = Spawn("TetherEntity", TransformSystem.GetMapCoordinates(target));
  182. var tetherPhysics = Comp<PhysicsComponent>(tether);
  183. component.TetherEntity = tether;
  184. _physics.WakeBody(tether);
  185. var joint = _joints.CreateMouseJoint(tether, target, id: TetherJoint);
  186. SharedJointSystem.LinearStiffness(component.Frequency, component.DampingRatio, tetherPhysics.Mass, targetPhysics.Mass, out var stiffness, out var damping);
  187. joint.Stiffness = stiffness;
  188. joint.Damping = damping;
  189. joint.MaxForce = component.MaxForce;
  190. // Sad...
  191. if (_netManager.IsServer && component.Stream == null)
  192. component.Stream = _audio.PlayPredicted(component.Sound, gunUid, null)?.Entity;
  193. Dirty(target, tethered);
  194. Dirty(gunUid, component);
  195. }
  196. protected virtual void StopTether(EntityUid gunUid, BaseForceGunComponent component, bool land = true, bool transfer = false)
  197. {
  198. if (component.Tethered == null)
  199. return;
  200. if (component.TetherEntity != null)
  201. {
  202. _joints.RemoveJoint(component.TetherEntity.Value, TetherJoint);
  203. if (_netManager.IsServer)
  204. QueueDel(component.TetherEntity.Value);
  205. component.TetherEntity = null;
  206. }
  207. if (TryComp<PhysicsComponent>(component.Tethered, out var targetPhysics))
  208. {
  209. if (land)
  210. {
  211. var thrown = EnsureComp<ThrownItemComponent>(component.Tethered.Value);
  212. _thrown.LandComponent(component.Tethered.Value, thrown, targetPhysics, true);
  213. _thrown.StopThrow(component.Tethered.Value, thrown);
  214. }
  215. _physics.SetBodyStatus(component.Tethered.Value, targetPhysics, BodyStatus.OnGround);
  216. _physics.SetSleepingAllowed(component.Tethered.Value, targetPhysics, true);
  217. _physics.SetAngularDamping(component.Tethered.Value, targetPhysics, Comp<TetheredComponent>(component.Tethered.Value).OriginalAngularDamping);
  218. }
  219. if (!transfer)
  220. {
  221. _audio.Stop(component.Stream);
  222. component.Stream = null;
  223. }
  224. TryComp<AppearanceComponent>(gunUid, out var appearance);
  225. _appearance.SetData(gunUid, TetherVisualsStatus.Key, false, appearance);
  226. _appearance.SetData(gunUid, ToggleableLightVisuals.Enabled, false, appearance);
  227. RemComp<TetheredComponent>(component.Tethered.Value);
  228. _blocker.UpdateCanMove(component.Tethered.Value);
  229. component.Tethered = null;
  230. Dirty(gunUid, component);
  231. }
  232. [Serializable, NetSerializable]
  233. protected sealed class RequestTetherMoveEvent : EntityEventArgs
  234. {
  235. public NetCoordinates Coordinates;
  236. }
  237. [Serializable, NetSerializable]
  238. public enum TetherVisualsStatus : byte
  239. {
  240. Key,
  241. }
  242. }