using System.Numerics; using Content.Server.Singularity.Components; using Content.Shared.Atmos.Components; using Content.Shared.Ghost; using Content.Shared.Physics; using Content.Shared.Singularity.EntitySystems; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; namespace Content.Server.Singularity.EntitySystems; /// /// The server side version of . /// Primarily responsible for managing s. /// Handles the gravitational pulses they can emit. /// public sealed class GravityWellSystem : SharedGravityWellSystem { #region Dependencies [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IViewVariablesManager _vvManager = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; #endregion Dependencies /// /// The minimum range at which gravpulses will act. /// Prevents division by zero problems. /// public const float MinGravPulseRange = 0.00001f; private EntityQuery _wellQuery; private EntityQuery _mapQuery; private EntityQuery _gridQuery; private EntityQuery _physicsQuery; private HashSet _entSet = new(); public override void Initialize() { base.Initialize(); _wellQuery = GetEntityQuery(); _mapQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); _physicsQuery = GetEntityQuery(); SubscribeLocalEvent(OnGravityWellMapInit); var vvHandle = _vvManager.GetTypeHandler(); vvHandle.AddPath(nameof(GravityWellComponent.TargetPulsePeriod), (_, comp) => comp.TargetPulsePeriod, SetPulsePeriod); } private void OnGravityWellMapInit(Entity ent, ref MapInitEvent args) { ent.Comp.NextPulseTime = _timing.CurTime + ent.Comp.TargetPulsePeriod; } public override void Shutdown() { var vvHandle = _vvManager.GetTypeHandler(); vvHandle.RemovePath(nameof(GravityWellComponent.TargetPulsePeriod)); base.Shutdown(); } /// /// Updates the pulse cooldowns of all gravity wells. /// If they are off cooldown it makes them emit a gravitational pulse and reset their cooldown. /// /// The time elapsed since the last set of updates. public override void Update(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var gravWell, out var xform)) { var curTime = _timing.CurTime; if (gravWell.NextPulseTime <= curTime) Update(uid, curTime - gravWell.LastPulseTime, gravWell, xform); } } /// /// Makes a gravity well emit a gravitational pulse and puts it on cooldown. /// The longer since the last gravitational pulse the more force it applies on affected entities. /// /// The uid of the gravity well to make pulse. /// The state of the gravity well to make pulse. /// The transform of the gravity well to make pulse. private void Update(EntityUid uid, GravityWellComponent? gravWell = null, TransformComponent? xform = null) { if (Resolve(uid, ref gravWell)) Update(uid, _timing.CurTime - gravWell.LastPulseTime, gravWell, xform); } /// /// Makes a gravity well emit a gravitational pulse and puts it on cooldown. /// /// The uid of the gravity well to make pulse. /// The state of the gravity well to make pulse. /// The amount to consider as having passed since the last gravitational pulse by the gravity well. Pulse force scales with this. /// The transform of the gravity well to make pulse. private void Update(EntityUid uid, TimeSpan frameTime, GravityWellComponent? gravWell = null, TransformComponent? xform = null) { if(!Resolve(uid, ref gravWell)) return; gravWell.NextPulseTime += gravWell.TargetPulsePeriod; if (gravWell.MaxRange < 0.0f || !Resolve(uid, ref xform)) return; var scale = (float)frameTime.TotalSeconds; GravPulse(uid, gravWell.MaxRange, gravWell.MinRange, gravWell.BaseRadialAcceleration * scale, gravWell.BaseTangentialAcceleration * scale, xform); } #region GravPulse /// /// Checks whether an entity can be affected by gravity pulses. /// TODO: Make this an event or such. /// /// The entity to check. private bool CanGravPulseAffect(EntityUid entity) { if (_physicsQuery.TryComp(entity, out var physics)) { if (physics.CollisionLayer == (int) CollisionGroup.GhostImpassable) return false; } return !(_gridQuery.HasComp(entity) || _mapQuery.HasComp(entity) || _wellQuery.HasComp(entity) ); } /// /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The entity at the epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. /// (optional) The transform of the entity at the epicenter of the gravitational pulse. public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV, TransformComponent? xform = null) { if (Resolve(uid, ref xform)) GravPulse(xform.Coordinates, maxRange, minRange, in baseMatrixDeltaV); } /// /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The entity at the epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base radial velocity that will be added to entities within range towards the center of the gravitational pulse. /// The base tangential velocity that will be added to entities within countrclockwise around the center of the gravitational pulse. /// (optional) The transform of the entity at the epicenter of the gravitational pulse. public void GravPulse(EntityUid uid, float maxRange, float minRange, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f, TransformComponent? xform = null) { if (Resolve(uid, ref xform)) GravPulse(xform.Coordinates, maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV); } /// /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, in baseMatrixDeltaV); /// /// Greates a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base radial velocity that will be added to entities within range towards the center of the gravitational pulse. /// The base tangential velocity that will be added to entities within countrclockwise around the center of the gravitational pulse. public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) => GravPulse(_transform.ToMapCoordinates(entityPos), maxRange, minRange, baseRadialDeltaV, baseTangentialDeltaV); /// /// Causes a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) { if (mapPos == MapCoordinates.Nullspace) return; // No gravpulses in nullspace please. _entSet.Clear(); var epicenter = mapPos.Position; 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. _lookup.GetEntitiesInRange(mapPos.MapId, epicenter, maxRange, _entSet, flags: LookupFlags.Dynamic | LookupFlags.Sundries); foreach (var entity in _entSet) { if (!_physicsQuery.TryGetComponent(entity, out var physics)) { continue; } if (TryComp(entity, out var movedPressure) && !movedPressure.Enabled) //Ignore magboots users continue; if(!CanGravPulseAffect(entity)) continue; var displacement = epicenter - _transform.GetWorldPosition(entity); var distance2 = displacement.LengthSquared(); if (distance2 < minRange2) continue; var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants. _physics.ApplyLinearImpulse(entity, Vector2.TransformNormal(displacement, baseMatrixDeltaV) * scaling, body: physics); } } /// /// Causes a gravitational pulse, shoving around all entities within some distance of an epicenter. /// /// The epicenter of the gravity pulse. /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors. /// The base amount of velocity that will be added to entities in range towards the epicenter of the pulse. /// The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) => GravPulse(mapPos, maxRange, minRange, new Matrix3x2(baseRadialDeltaV, -baseTangentialDeltaV, baseTangentialDeltaV, baseRadialDeltaV, 0.0f, 0.0f)); #endregion GravPulse #region Getters/Setters /// /// Sets the pulse period for a gravity well. /// If the new pulse period implies that the gravity well was intended to pulse already it does so immediately. /// /// The uid of the gravity well to set the pulse period for. /// The new pulse period for the gravity well. /// The state of the gravity well to set the pulse period for. public void SetPulsePeriod(EntityUid uid, TimeSpan value, GravityWellComponent? gravWell = null) { if(!Resolve(uid, ref gravWell)) return; if (MathHelper.CloseTo(gravWell.TargetPulsePeriod.TotalSeconds, value.TotalSeconds)) return; gravWell.TargetPulsePeriod = value; gravWell.NextPulseTime = gravWell.LastPulseTime + gravWell.TargetPulsePeriod; var curTime = _timing.CurTime; if (gravWell.NextPulseTime <= curTime) Update(uid, curTime - gravWell.LastPulseTime, gravWell); } #endregion Getters/Setters }