using Content.Server.Physics.Components; using Content.Server.Power.EntitySystems; using Content.Server.Singularity.Components; using Content.Shared.Singularity.Components; using Robust.Shared.Map; using Robust.Shared.Timing; using System.Numerics; namespace Content.Server.Singularity.EntitySystems; /// /// Handles singularity attractors. /// public sealed class SingularityAttractorSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; /// /// The minimum range at which the attraction will act. /// Prevents division by zero problems. /// public const float MinAttractRange = 0.00001f; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); } /// /// Updates the pulse cooldowns of all singularity attractors. /// If they are off cooldown it makes them emit an attraction pulse and reset their cooldown. /// /// The time elapsed since the last set of updates. public override void Update(float frameTime) { if (!_timing.IsFirstTimePredicted) return; var query = EntityQueryEnumerator(); var now = _timing.CurTime; while (query.MoveNext(out var uid, out var attractor, out var xform)) { if (attractor.LastPulseTime + attractor.TargetPulsePeriod <= now) Update(uid, attractor, xform); } } /// /// Makes an attractor attract all singularities and puts it on cooldown. /// /// The uid of the attractor to make pulse. /// The state of the attractor to make pulse. /// The transform of the attractor to make pulse. private void Update(EntityUid uid, SingularityAttractorComponent? attractor = null, TransformComponent? xform = null) { if (!Resolve(uid, ref attractor, ref xform)) return; if (!this.IsPowered(uid, EntityManager)) return; attractor.LastPulseTime = _timing.CurTime; var mapPos = xform.Coordinates.ToMap(EntityManager, _transform); if (mapPos == MapCoordinates.Nullspace) return; var query = EntityQuery(); foreach (var (singulo, walk, singuloXform) in query) { var singuloMapPos = singuloXform.Coordinates.ToMap(EntityManager, _transform); if (singuloMapPos.MapId != mapPos.MapId) continue; var biasBy = mapPos.Position - singuloMapPos.Position; var length = biasBy.Length(); if (length <= MinAttractRange) return; biasBy = Vector2.Normalize(biasBy) * (attractor.BaseRange / length); walk.BiasVector += biasBy; } } /// /// Resets the pulse timings of the attractor when the component starts up. /// /// The uid of the attractor to start up. /// The state of the attractor to start up. /// The startup prompt arguments. private void OnMapInit(Entity ent, ref MapInitEvent args) { ent.Comp.LastPulseTime = _timing.CurTime; } }