using Content.Shared.Interaction; using Content.Shared.Pinpointer; using System.Linq; using Robust.Shared.Utility; namespace Content.Server.Pinpointer { public sealed class ServerPinpointerSystem : SharedPinpointerSystem { [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnActivate); } private void OnActivate(EntityUid uid, PinpointerComponent component, ActivateInWorldEvent args) { TogglePinpointer(uid, component); // try to find target from whitelist if (component.IsActive && component.Component != null) { if (!EntityManager.ComponentFactory.TryGetRegistration(component.Component, out var reg)) { Logger.Error($"Unable to find component registration for {component.Component} for pinpointer!"); DebugTools.Assert(false); return; } var target = FindTargetFromComponent(uid, reg.Type); SetTarget(uid, target, component); } } public override void Update(float frameTime) { base.Update(frameTime); // because target or pinpointer can move // we need to update pinpointers arrow each frame foreach (var uid in ActivePinpointers) { UpdateDirectionToTarget(uid); } } /// /// Try to find the closest entity from whitelist on a current map /// Will return null if can't find anything /// private EntityUid? FindTargetFromComponent(EntityUid uid, Type whitelist, TransformComponent? transform = null) { var xformQuery = GetEntityQuery(); if (transform == null) xformQuery.TryGetComponent(uid, out transform); if (transform == null) return null; // sort all entities in distance increasing order var mapId = transform.MapID; var l = new SortedList(); var worldPos = _transform.GetWorldPosition(transform, xformQuery); foreach (var comp in EntityManager.GetAllComponents(whitelist)) { if (!xformQuery.TryGetComponent(comp.Owner, out var compXform) || compXform.MapID != mapId) continue; var dist = (_transform.GetWorldPosition(compXform, xformQuery) - worldPos).LengthSquared; l.TryAdd(dist, comp.Owner); } // return uid with a smallest distacne return l.Count > 0 ? l.First().Value : null; } /// /// Set pinpointers target to track /// public void SetTarget(EntityUid uid, EntityUid? target, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; if (pinpointer.Target == target) return; pinpointer.Target = target; if (pinpointer.IsActive) UpdateDirectionToTarget(uid, pinpointer); } /// /// Update direction from pinpointer to selected target (if it was set) /// private void UpdateDirectionToTarget(EntityUid uid, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return; var target = pinpointer.Target; if (target == null || !EntityManager.EntityExists(target.Value)) { SetDirection(uid, Direction.Invalid, pinpointer); SetDistance(uid, Distance.UNKNOWN, pinpointer); return; } var dirVec = CalculateDirection(uid, target.Value); if (dirVec != null) { var dir = dirVec.Value.GetDir(); SetDirection(uid, dir, pinpointer); var dist = CalculateDistance(uid, dirVec.Value, pinpointer); SetDistance(uid, dist, pinpointer); } else { SetDirection(uid, Direction.Invalid, pinpointer); SetDistance(uid, Distance.UNKNOWN, pinpointer); } } /// /// Calculate direction from pinUid to trgUid /// /// Null if failed to caluclate 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(EntityUid uid, Vector2 vec, PinpointerComponent? pinpointer = null) { if (!Resolve(uid, ref pinpointer)) return Distance.UNKNOWN; 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; } } }