GravityWellSystem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. using System.Numerics;
  2. using Content.Server.Singularity.Components;
  3. using Content.Shared.Atmos.Components;
  4. using Content.Shared.Ghost;
  5. using Content.Shared.Physics;
  6. using Content.Shared.Singularity.EntitySystems;
  7. using Robust.Shared.Map;
  8. using Robust.Shared.Map.Components;
  9. using Robust.Shared.Physics;
  10. using Robust.Shared.Physics.Components;
  11. using Robust.Shared.Physics.Systems;
  12. using Robust.Shared.Timing;
  13. namespace Content.Server.Singularity.EntitySystems;
  14. /// <summary>
  15. /// The server side version of <see cref="SharedGravityWellSystem"/>.
  16. /// Primarily responsible for managing <see cref="GravityWellComponent"/>s.
  17. /// Handles the gravitational pulses they can emit.
  18. /// </summary>
  19. public sealed class GravityWellSystem : SharedGravityWellSystem
  20. {
  21. #region Dependencies
  22. [Dependency] private readonly IGameTiming _timing = default!;
  23. [Dependency] private readonly IViewVariablesManager _vvManager = default!;
  24. [Dependency] private readonly EntityLookupSystem _lookup = default!;
  25. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  26. [Dependency] private readonly SharedTransformSystem _transform = default!;
  27. #endregion Dependencies
  28. /// <summary>
  29. /// The minimum range at which gravpulses will act.
  30. /// Prevents division by zero problems.
  31. /// </summary>
  32. public const float MinGravPulseRange = 0.00001f;
  33. private EntityQuery<GravityWellComponent> _wellQuery;
  34. private EntityQuery<MapComponent> _mapQuery;
  35. private EntityQuery<MapGridComponent> _gridQuery;
  36. private EntityQuery<PhysicsComponent> _physicsQuery;
  37. private HashSet<EntityUid> _entSet = new();
  38. public override void Initialize()
  39. {
  40. base.Initialize();
  41. _wellQuery = GetEntityQuery<GravityWellComponent>();
  42. _mapQuery = GetEntityQuery<MapComponent>();
  43. _gridQuery = GetEntityQuery<MapGridComponent>();
  44. _physicsQuery = GetEntityQuery<PhysicsComponent>();
  45. SubscribeLocalEvent<GravityWellComponent, MapInitEvent>(OnGravityWellMapInit);
  46. var vvHandle = _vvManager.GetTypeHandler<GravityWellComponent>();
  47. vvHandle.AddPath(nameof(GravityWellComponent.TargetPulsePeriod), (_, comp) => comp.TargetPulsePeriod, SetPulsePeriod);
  48. }
  49. private void OnGravityWellMapInit(Entity<GravityWellComponent> ent, ref MapInitEvent args)
  50. {
  51. ent.Comp.NextPulseTime = _timing.CurTime + ent.Comp.TargetPulsePeriod;
  52. }
  53. public override void Shutdown()
  54. {
  55. var vvHandle = _vvManager.GetTypeHandler<GravityWellComponent>();
  56. vvHandle.RemovePath(nameof(GravityWellComponent.TargetPulsePeriod));
  57. base.Shutdown();
  58. }
  59. /// <summary>
  60. /// Updates the pulse cooldowns of all gravity wells.
  61. /// If they are off cooldown it makes them emit a gravitational pulse and reset their cooldown.
  62. /// </summary>
  63. /// <param name="frameTime">The time elapsed since the last set of updates.</param>
  64. public override void Update(float frameTime)
  65. {
  66. if(!_timing.IsFirstTimePredicted)
  67. return;
  68. var query = EntityQueryEnumerator<GravityWellComponent, TransformComponent>();
  69. while (query.MoveNext(out var uid, out var gravWell, out var xform))
  70. {
  71. var curTime = _timing.CurTime;
  72. if (gravWell.NextPulseTime <= curTime)
  73. Update(uid, curTime - gravWell.LastPulseTime, gravWell, xform);
  74. }
  75. }
  76. /// <summary>
  77. /// Makes a gravity well emit a gravitational pulse and puts it on cooldown.
  78. /// The longer since the last gravitational pulse the more force it applies on affected entities.
  79. /// </summary>
  80. /// <param name="uid">The uid of the gravity well to make pulse.</param>
  81. /// <param name="gravWell">The state of the gravity well to make pulse.</param>
  82. /// <param name="xform">The transform of the gravity well to make pulse.</param>
  83. private void Update(EntityUid uid, GravityWellComponent? gravWell = null, TransformComponent? xform = null)
  84. {
  85. if (Resolve(uid, ref gravWell))
  86. Update(uid, _timing.CurTime - gravWell.LastPulseTime, gravWell, xform);
  87. }
  88. /// <summary>
  89. /// Makes a gravity well emit a gravitational pulse and puts it on cooldown.
  90. /// </summary>
  91. /// <param name="uid">The uid of the gravity well to make pulse.</param>
  92. /// <param name="gravWell">The state of the gravity well to make pulse.</param>
  93. /// <param name="frameTime">The amount to consider as having passed since the last gravitational pulse by the gravity well. Pulse force scales with this.</param>
  94. /// <param name="xform">The transform of the gravity well to make pulse.</param>
  95. private void Update(EntityUid uid, TimeSpan frameTime, GravityWellComponent? gravWell = null, TransformComponent? xform = null)
  96. {
  97. if(!Resolve(uid, ref gravWell))
  98. return;
  99. gravWell.NextPulseTime += gravWell.TargetPulsePeriod;
  100. if (gravWell.MaxRange < 0.0f || !Resolve(uid, ref xform))
  101. return;
  102. var scale = (float)frameTime.TotalSeconds;
  103. GravPulse(uid, gravWell.MaxRange, gravWell.MinRange, gravWell.BaseRadialAcceleration * scale, gravWell.BaseTangentialAcceleration * scale, xform);
  104. }
  105. #region GravPulse
  106. /// <summary>
  107. /// Checks whether an entity can be affected by gravity pulses.
  108. /// TODO: Make this an event or such.
  109. /// </summary>
  110. /// <param name="entity">The entity to check.</param>
  111. private bool CanGravPulseAffect(EntityUid entity)
  112. {
  113. if (_physicsQuery.TryComp(entity, out var physics))
  114. {
  115. if (physics.CollisionLayer == (int) CollisionGroup.GhostImpassable)
  116. return false;
  117. }
  118. return !(_gridQuery.HasComp(entity) ||
  119. _mapQuery.HasComp(entity) ||
  120. _wellQuery.HasComp(entity)
  121. );
  122. }
  123. /// <summary>
  124. /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter.
  125. /// </summary>
  126. /// <param name="uid">The entity at the epicenter of the gravity pulse.</param>
  127. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  128. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse.</param>
  129. /// <param name="baseMatrixDeltaV">The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter.</param>
  130. /// <param name="xform">(optional) The transform of the entity at the epicenter of the gravitational pulse.</param>
  131. public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV, TransformComponent? xform = null)
  132. {
  133. if (Resolve(uid, ref xform))
  134. GravPulse(xform.Coordinates, maxRange, minRange, in baseMatrixDeltaV);
  135. }
  136. /// <summary>
  137. /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter.
  138. /// </summary>
  139. /// <param name="uid">The entity at the epicenter of the gravity pulse.</param>
  140. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  141. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse.</param>
  142. /// <param name="baseRadialDeltaV">The base radial velocity that will be added to entities within range towards the center of the gravitational pulse.</param>
  143. /// <param name="baseTangentialDeltaV">The base tangential velocity that will be added to entities within countrclockwise around the center of the gravitational pulse.</param>
  144. /// <param name="xform">(optional) The transform of the entity at the epicenter of the gravitational pulse.</param>
  145. public void GravPulse(EntityUid uid, float maxRange, float minRange, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f, TransformComponent? xform = null)
  146. {
  147. if (Resolve(uid, ref xform))
  148. GravPulse(xform.Coordinates, maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV);
  149. }
  150. /// <summary>
  151. /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter.
  152. /// </summary>
  153. /// <param name="entityPos">The epicenter of the gravity pulse.</param>
  154. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  155. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse.</param>
  156. /// <param name="baseMatrixDeltaV">The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter.</param>
  157. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV)
  158. => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, in baseMatrixDeltaV);
  159. /// <summary>
  160. /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter.
  161. /// </summary>
  162. /// <param name="entityPos">The epicenter of the gravity pulse.</param>
  163. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  164. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse.</param>
  165. /// <param name="baseRadialDeltaV">The base radial velocity that will be added to entities within range towards the center of the gravitational pulse.</param>
  166. /// <param name="baseTangentialDeltaV">The base tangential velocity that will be added to entities within countrclockwise around the center of the gravitational pulse.</param>
  167. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f)
  168. => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV);
  169. /// <summary>
  170. /// Causes a gravitational pulse, shoving around all entities within some distance of an epicenter.
  171. /// </summary>
  172. /// <param name="mapPos">The epicenter of the gravity pulse.</param>
  173. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  174. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors.</param>
  175. /// <param name="baseMatrixDeltaV">The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter.</param>
  176. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV)
  177. {
  178. if (mapPos == MapCoordinates.Nullspace)
  179. return; // No gravpulses in nullspace please.
  180. _entSet.Clear();
  181. var epicenter = mapPos.Position;
  182. var minRange2 = MathF.Max(minRange * minRange, MinGravPulseRange); // Cache square value for speed. Also apply a sane minimum value to the minimum value so that div/0s don't happen.
  183. _lookup.GetEntitiesInRange(mapPos.MapId,
  184. epicenter,
  185. maxRange,
  186. _entSet,
  187. flags: LookupFlags.Dynamic | LookupFlags.Sundries);
  188. foreach (var entity in _entSet)
  189. {
  190. if (!_physicsQuery.TryGetComponent(entity, out var physics))
  191. {
  192. continue;
  193. }
  194. if (TryComp<MovedByPressureComponent>(entity, out var movedPressure) && !movedPressure.Enabled) //Ignore magboots users
  195. continue;
  196. if(!CanGravPulseAffect(entity))
  197. continue;
  198. var displacement = epicenter - _transform.GetWorldPosition(entity);
  199. var distance2 = displacement.LengthSquared();
  200. if (distance2 < minRange2)
  201. continue;
  202. var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants.
  203. _physics.ApplyLinearImpulse(entity, Vector2.TransformNormal(displacement, baseMatrixDeltaV) * scaling, body: physics);
  204. }
  205. }
  206. /// <summary>
  207. /// Causes a gravitational pulse, shoving around all entities within some distance of an epicenter.
  208. /// </summary>
  209. /// <param name="mapPos">The epicenter of the gravity pulse.</param>
  210. /// <param name="maxRange">The maximum distance at which entities can be affected by the gravity pulse.</param>
  211. /// <param name="minRange">The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors.</param>
  212. /// <param name="baseRadialDeltaV">The base amount of velocity that will be added to entities in range towards the epicenter of the pulse.</param>
  213. /// <param name="baseTangentialDeltaV">The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse.</param>
  214. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f)
  215. => GravPulse(mapPos, maxRange, minRange, new Matrix3x2(baseRadialDeltaV, -baseTangentialDeltaV, baseTangentialDeltaV, baseRadialDeltaV, 0.0f, 0.0f));
  216. #endregion GravPulse
  217. #region Getters/Setters
  218. /// <summary>
  219. /// Sets the pulse period for a gravity well.
  220. /// If the new pulse period implies that the gravity well was intended to pulse already it does so immediately.
  221. /// </summary>
  222. /// <param name="uid">The uid of the gravity well to set the pulse period for.</param>
  223. /// <param name="value">The new pulse period for the gravity well.</param>
  224. /// <param name="gravWell">The state of the gravity well to set the pulse period for.</param>
  225. public void SetPulsePeriod(EntityUid uid, TimeSpan value, GravityWellComponent? gravWell = null)
  226. {
  227. if(!Resolve(uid, ref gravWell))
  228. return;
  229. if (MathHelper.CloseTo(gravWell.TargetPulsePeriod.TotalSeconds, value.TotalSeconds))
  230. return;
  231. gravWell.TargetPulsePeriod = value;
  232. gravWell.NextPulseTime = gravWell.LastPulseTime + gravWell.TargetPulsePeriod;
  233. var curTime = _timing.CurTime;
  234. if (gravWell.NextPulseTime <= curTime)
  235. Update(uid, curTime - gravWell.LastPulseTime, gravWell);
  236. }
  237. #endregion Getters/Setters
  238. }