using Content.Shared.Interaction; using Content.Shared.Pinpointer; using System.Linq; using System.Numerics; using Robust.Shared.Utility; using Content.Server.Shuttles.Events; namespace Content.Server.Pinpointer; public sealed class PinpointerSystem : SharedPinpointerSystem { [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; private EntityQuery _xformQuery; public override void Initialize() { base.Initialize(); _xformQuery = GetEntityQuery(); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnLocateTarget); } public override bool TogglePinpointer(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return false; var isActive = !ent.Comp.IsActive; SetActive(ent, isActive); UpdateAppearance(ent); return isActive; } private void UpdateAppearance(Entity ent) { if (!Resolve(ent, ref ent.Comp1) || !Resolve(ent, ref ent.Comp2)) return; _appearance.SetData(ent, PinpointerVisuals.IsActive, ent.Comp1.IsActive, ent.Comp2); _appearance.SetData(ent, PinpointerVisuals.TargetDistance, ent.Comp1.DistanceToTarget, ent.Comp2); } private void OnActivate(Entity ent, ref ActivateInWorldEvent args) { if (args.Handled || !args.Complex) return; TogglePinpointer(ent.AsNullable()); if (!ent.Comp.CanRetarget) LocateTarget(ent); args.Handled = true; } private void OnLocateTarget(ref FTLCompletedEvent ev) { // This feels kind of expensive, but it only happens once per hyperspace jump // todo: ideally, you would need to raise this event only on jumped entities // this code update ALL pinpointers in game var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var pinpointer)) { if (pinpointer.CanRetarget) continue; LocateTarget((uid, pinpointer)); } } private void LocateTarget(Entity ent) { var component = ent.Comp; // try to find target from whitelist if (component.IsActive && component.Component != null) { if (!EntityManager.ComponentFactory.TryGetRegistration(component.Component, out var reg)) { Log.Error($"Unable to find component registration for {component.Component} for pinpointer!"); DebugTools.Assert(false); return; } var target = FindTargetFromComponent(ent.Owner, reg.Type); SetTarget(ent.AsNullable(), target); } } public override void Update(float frameTime) { base.Update(frameTime); // because target or pinpointer can move // we need to update pinpointers arrow each frame var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var pinpointer)) { UpdateDirectionToTarget((uid, pinpointer)); } } /// /// Try to find the closest entity from whitelist on a current map /// Will return null if can't find anything /// private EntityUid? FindTargetFromComponent(Entity ent, Type whitelist) { if (!Resolve(ent, ref ent.Comp)) return null; // sort all entities in distance increasing order var mapId = ent.Comp.MapID; var l = new SortedList(); var worldPos = _transform.GetWorldPosition(ent.Comp); foreach (var (otherUid, _) in EntityManager.GetAllComponents(whitelist)) { if (!_xformQuery.TryGetComponent(otherUid, out var compXform) || compXform.MapID != mapId) continue; var dist = (_transform.GetWorldPosition(compXform) - worldPos).LengthSquared(); l.TryAdd(dist, otherUid); } // return uid with a smallest distance return l.Count > 0 ? l.First().Value : null; } /// /// Update direction from pinpointer to selected target (if it was set) /// protected override void UpdateDirectionToTarget(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return; var pinpointer = ent.Comp; if (!pinpointer.IsActive) return; var target = pinpointer.Target; if (target == null || !Exists(target.Value)) { SetDistance(ent, Distance.Unknown); return; } var dirVec = CalculateDirection(ent, target.Value); var oldDist = pinpointer.DistanceToTarget; if (dirVec != null) { var angle = dirVec.Value.ToWorldAngle(); TrySetArrowAngle(ent, angle); var dist = CalculateDistance(dirVec.Value, pinpointer); SetDistance(ent, dist); } else { SetDistance(ent, Distance.Unknown); } if (oldDist != pinpointer.DistanceToTarget) UpdateAppearance(ent); } /// /// Calculate direction from pinUid to trgUid /// /// Null if failed to calculate distance between two entities private Vector2? CalculateDirection(EntityUid pinUid, EntityUid trgUid) { var xformQuery = GetEntityQuery(); // check if entities have transform component if (!xformQuery.TryGetComponent(pinUid, out var pin)) return null; if (!xformQuery.TryGetComponent(trgUid, out var trg)) return null; // check if they are on same map if (pin.MapID != trg.MapID) return null; // get world direction vector var dir = _transform.GetWorldPosition(trg, xformQuery) - _transform.GetWorldPosition(pin, xformQuery); return dir; } private Distance CalculateDistance(Vector2 vec, PinpointerComponent pinpointer) { var dist = vec.Length(); if (dist <= pinpointer.ReachedDistance) return Distance.Reached; else if (dist <= pinpointer.CloseDistance) return Distance.Close; else if (dist <= pinpointer.MediumDistance) return Distance.Medium; else return Distance.Far; } }