CatStation/Content.Server/Pinpointer/PinpointerSystem.cs
Eveloop 22a547c7c8
Use dependency injection for EntityQuery<T>s in Content.Server (#43566)
* Replace usages of EntityQuery with dependency injected ones in Content.Server

* Remove unused EntityQuery dependencies

* Restore CheckPressureAndFire override method

* Revert EntityQuery refactor changes to DisposalTubeSystem.cs & DisposableSystem.cs

* Infer Transform/Metadata directly instead of passing (#43479)

* Resolve RA0049, RA0051 errors (#43479)

* Resolve RA0049, RA0051 errors (#43479)

* Apply suggestions from code review

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2026-05-17 15:59:37 +00:00

202 lines
6.4 KiB
C#

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 partial class PinpointerSystem : SharedPinpointerSystem
{
[Dependency] private SharedTransformSystem _transform = default!;
[Dependency] private SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PinpointerComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<FTLCompletedEvent>(OnLocateTarget);
}
public override bool TogglePinpointer(Entity<PinpointerComponent?> 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<PinpointerComponent?, AppearanceComponent?> 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<PinpointerComponent> 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<PinpointerComponent>();
while (query.MoveNext(out var uid, out var pinpointer))
{
if (pinpointer.CanRetarget)
continue;
LocateTarget((uid, pinpointer));
}
}
private void LocateTarget(Entity<PinpointerComponent> 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<PinpointerComponent>();
while (query.MoveNext(out var uid, out var pinpointer))
{
UpdateDirectionToTarget((uid, pinpointer));
}
}
/// <summary>
/// Try to find the closest entity from whitelist on a current map
/// Will return null if can't find anything
/// </summary>
private EntityUid? FindTargetFromComponent(Entity<TransformComponent?> 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<float, EntityUid>();
var worldPos = _transform.GetWorldPosition(ent.Comp);
foreach (var (otherUid, _) in EntityManager.GetAllComponents(whitelist))
{
if (!TryComp(otherUid, out TransformComponent? 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;
}
/// <summary>
/// Update direction from pinpointer to selected target (if it was set)
/// </summary>
protected override void UpdateDirectionToTarget(Entity<PinpointerComponent?> 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);
}
/// <summary>
/// Calculate direction from pinUid to trgUid
/// </summary>
/// <returns>Null if failed to calculate distance between two entities</returns>
private Vector2? CalculateDirection(EntityUid pinUid, EntityUid trgUid)
{
// check if entities have transform component
if (!TryComp(pinUid, out TransformComponent? pin))
return null;
if (!TryComp(trgUid, out TransformComponent? 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) - _transform.GetWorldPosition(pin);
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;
}
}