Fix pinpointer flicker (#15452)
This commit is contained in:
@@ -1,78 +1,41 @@
|
|||||||
using Content.Shared.Pinpointer;
|
using Content.Shared.Pinpointer;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer
|
namespace Content.Client.Pinpointer;
|
||||||
|
|
||||||
|
public sealed class PinpointerSystem : SharedPinpointerSystem
|
||||||
{
|
{
|
||||||
public sealed class PinpointerSystem : SharedPinpointerSystem
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
base.Update(frameTime);
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
// we want to show pinpointers arrow direction relative
|
||||||
|
// to players eye rotation (like it was in SS13)
|
||||||
|
|
||||||
|
// because eye can change it rotation anytime
|
||||||
|
// we need to update this arrow in a update loop
|
||||||
|
var query = EntityQueryEnumerator<PinpointerComponent, SpriteComponent>();
|
||||||
|
while (query.MoveNext(out var _, out var pinpointer, out var sprite))
|
||||||
{
|
{
|
||||||
base.Initialize();
|
if (!pinpointer.HasTarget)
|
||||||
SubscribeLocalEvent<PinpointerComponent, ComponentHandleState>(HandleCompState);
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
public override void FrameUpdate(float frameTime)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(frameTime);
|
|
||||||
|
|
||||||
// we want to show pinpointers arrow direction relative
|
|
||||||
// to players eye rotation (like it was in SS13)
|
|
||||||
|
|
||||||
// because eye can change it rotation anytime
|
|
||||||
// we need to update this arrow in a update loop
|
|
||||||
foreach (var pinpointer in EntityQuery<PinpointerComponent>())
|
|
||||||
{
|
|
||||||
UpdateAppearance(pinpointer.Owner, pinpointer);
|
|
||||||
UpdateEyeDir(pinpointer.Owner, pinpointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleCompState(EntityUid uid, PinpointerComponent pinpointer, ref ComponentHandleState args)
|
|
||||||
{
|
|
||||||
if (args.Current is not PinpointerComponentState state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pinpointer.IsActive = state.IsActive;
|
|
||||||
pinpointer.ArrowAngle = state.ArrowAngle;
|
|
||||||
pinpointer.DistanceToTarget = state.DistanceToTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance(EntityUid uid, PinpointerComponent? pinpointer = null,
|
|
||||||
AppearanceComponent? appearance = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer, ref appearance))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_appearance.SetData(uid, PinpointerVisuals.IsActive, pinpointer.IsActive, appearance);
|
|
||||||
_appearance.SetData(uid, PinpointerVisuals.TargetDistance, pinpointer.DistanceToTarget, appearance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateArrowAngle(EntityUid uid, Angle angle, PinpointerComponent? pinpointer = null,
|
|
||||||
AppearanceComponent? appearance = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer, ref appearance))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_appearance.SetData(uid, PinpointerVisuals.ArrowAngle, angle, appearance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transform pinpointer arrow from world space to eye space
|
|
||||||
/// And send it to the appearance component
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateEyeDir(EntityUid uid, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer) || !pinpointer.HasTarget)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var eye = _eyeManager.CurrentEye;
|
var eye = _eyeManager.CurrentEye;
|
||||||
var angle = pinpointer.ArrowAngle + eye.Rotation;
|
var angle = pinpointer.ArrowAngle + eye.Rotation;
|
||||||
UpdateArrowAngle(uid, angle, pinpointer);
|
|
||||||
|
switch (pinpointer.DistanceToTarget)
|
||||||
|
{
|
||||||
|
case Distance.Close:
|
||||||
|
case Distance.Medium:
|
||||||
|
case Distance.Far:
|
||||||
|
sprite.LayerSetRotation(PinpointerLayers.Screen, angle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sprite.LayerSetRotation(PinpointerLayers.Screen, Angle.Zero);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
using Content.Shared.Pinpointer;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Pinpointer
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class PinpointerVisualizerSystem : VisualizerSystem<PinpointerComponent>
|
|
||||||
{
|
|
||||||
protected override void OnAppearanceChange(EntityUid uid, PinpointerComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
base.OnAppearanceChange(uid, component, ref args);
|
|
||||||
|
|
||||||
if (!TryComp(component.Owner, out SpriteComponent? sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// check if pinpointer screen is active
|
|
||||||
if (!AppearanceSystem.TryGetData<bool>(uid, PinpointerVisuals.IsActive, out var isActive, args.Component) || !isActive)
|
|
||||||
{
|
|
||||||
sprite.LayerSetVisible(PinpointerLayers.Screen, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sprite.LayerSetVisible(PinpointerLayers.Screen, true);
|
|
||||||
|
|
||||||
// check distance and direction to target
|
|
||||||
if (!AppearanceSystem.TryGetData<Distance>(uid, PinpointerVisuals.TargetDistance, out var dis, args.Component) ||
|
|
||||||
!AppearanceSystem.TryGetData<Angle>(uid, PinpointerVisuals.ArrowAngle, out var angle, args.Component))
|
|
||||||
{
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinonnull");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, Angle.Zero);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (dis)
|
|
||||||
{
|
|
||||||
case Distance.Reached:
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinondirect");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, Angle.Zero);
|
|
||||||
break;
|
|
||||||
case Distance.Close:
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinonclose");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, angle);
|
|
||||||
break;
|
|
||||||
case Distance.Medium:
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinonmedium");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, angle);
|
|
||||||
break;
|
|
||||||
case Distance.Far:
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinonfar");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, angle);
|
|
||||||
break;
|
|
||||||
case Distance.Unknown:
|
|
||||||
sprite.LayerSetState(PinpointerLayers.Screen, "pinonnull");
|
|
||||||
sprite.LayerSetRotation(PinpointerLayers.Screen, Angle.Zero);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,183 +4,201 @@ using System.Linq;
|
|||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Content.Server.Shuttles.Events;
|
using Content.Server.Shuttles.Events;
|
||||||
|
|
||||||
namespace Content.Server.Pinpointer
|
namespace Content.Server.Pinpointer;
|
||||||
|
|
||||||
|
public sealed class PinpointerSystem : SharedPinpointerSystem
|
||||||
{
|
{
|
||||||
public sealed class PinpointerSystem : SharedPinpointerSystem
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<PinpointerComponent, ActivateInWorldEvent>(OnActivate);
|
||||||
|
SubscribeLocalEvent<FTLCompletedEvent>(OnLocateTarget);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
private void OnActivate(EntityUid uid, PinpointerComponent component, ActivateInWorldEvent args)
|
||||||
|
{
|
||||||
|
TogglePinpointer(uid, component);
|
||||||
|
LocateTarget(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TogglePinpointer(EntityUid uid, PinpointerComponent? pinpointer = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref pinpointer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var isActive = !pinpointer.IsActive;
|
||||||
|
SetActive(uid, isActive, pinpointer);
|
||||||
|
UpdateAppearance(uid, pinpointer);
|
||||||
|
return isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance(EntityUid uid, PinpointerComponent pinpointer, AppearanceComponent? appearance = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref appearance))
|
||||||
|
return;
|
||||||
|
_appearance.SetData(uid, PinpointerVisuals.IsActive, pinpointer.IsActive, appearance);
|
||||||
|
_appearance.SetData(uid, PinpointerVisuals.TargetDistance, pinpointer.DistanceToTarget, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
base.Initialize();
|
LocateTarget(uid, pinpointer);
|
||||||
SubscribeLocalEvent<PinpointerComponent, ActivateInWorldEvent>(OnActivate);
|
|
||||||
SubscribeLocalEvent<FTLCompletedEvent>(OnLocateTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnActivate(EntityUid uid, PinpointerComponent component, ActivateInWorldEvent args)
|
|
||||||
{
|
|
||||||
TogglePinpointer(uid, component);
|
|
||||||
LocateTarget(uid, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
foreach (var pinpointer in EntityQuery<PinpointerComponent>())
|
|
||||||
{
|
|
||||||
LocateTarget(pinpointer.Owner, pinpointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LocateTarget(EntityUid uid, PinpointerComponent 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 pinpointer in EntityQuery<PinpointerComponent>())
|
|
||||||
{
|
|
||||||
UpdateDirectionToTarget(pinpointer.Owner, 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(EntityUid uid, Type whitelist, TransformComponent? transform = null)
|
|
||||||
{
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
|
|
||||||
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<float, EntityUid>();
|
|
||||||
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 distance
|
|
||||||
return l.Count > 0 ? l.First().Value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set pinpointers target to track
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update direction from pinpointer to selected target (if it was set)
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDirectionToTarget(EntityUid uid, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!pinpointer.IsActive)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var target = pinpointer.Target;
|
|
||||||
if (target == null || !EntityManager.EntityExists(target.Value))
|
|
||||||
{
|
|
||||||
SetDistance(uid, Distance.Unknown, pinpointer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dirVec = CalculateDirection(uid, target.Value);
|
|
||||||
if (dirVec != null)
|
|
||||||
{
|
|
||||||
var angle = dirVec.Value.ToWorldAngle();
|
|
||||||
TrySetArrowAngle(uid, angle, pinpointer);
|
|
||||||
var dist = CalculateDistance(uid, dirVec.Value, pinpointer);
|
|
||||||
SetDistance(uid, dist, pinpointer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetDistance(uid, Distance.Unknown, pinpointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LocateTarget(EntityUid uid, PinpointerComponent 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
|
||||||
|
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(EntityUid uid, Type whitelist, TransformComponent? transform = null)
|
||||||
|
{
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
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<float, EntityUid>();
|
||||||
|
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 distance
|
||||||
|
return l.Count > 0 ? l.First().Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set pinpointers target to track
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update direction from pinpointer to selected target (if it was set)
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDirectionToTarget(EntityUid uid, PinpointerComponent pinpointer)
|
||||||
|
{
|
||||||
|
if (!pinpointer.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var target = pinpointer.Target;
|
||||||
|
if (target == null || !EntityManager.EntityExists(target.Value))
|
||||||
|
{
|
||||||
|
SetDistance(uid, Distance.Unknown, pinpointer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirVec = CalculateDirection(uid, target.Value);
|
||||||
|
var oldDist = pinpointer.DistanceToTarget;
|
||||||
|
if (dirVec != null)
|
||||||
|
{
|
||||||
|
var angle = dirVec.Value.ToWorldAngle();
|
||||||
|
TrySetArrowAngle(uid, angle, pinpointer);
|
||||||
|
var dist = CalculateDistance(dirVec.Value, pinpointer);
|
||||||
|
SetDistance(uid, dist, pinpointer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetDistance(uid, Distance.Unknown, pinpointer);
|
||||||
|
}
|
||||||
|
if (oldDist != pinpointer.DistanceToTarget)
|
||||||
|
UpdateAppearance(uid, pinpointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
// 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 static 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,60 @@
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Pinpointer
|
namespace Content.Shared.Pinpointer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a sprite on the item that points towards the target component.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[AutoGenerateComponentState]
|
||||||
|
[Access(typeof(SharedPinpointerSystem))]
|
||||||
|
public sealed partial class PinpointerComponent : Component
|
||||||
{
|
{
|
||||||
|
// TODO: Type serializer oh god
|
||||||
|
[DataField("component")]
|
||||||
|
public string? Component;
|
||||||
|
|
||||||
|
[DataField("mediumDistance"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float MediumDistance = 16f;
|
||||||
|
|
||||||
|
[DataField("closeDistance"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float CloseDistance = 8f;
|
||||||
|
|
||||||
|
[DataField("reachedDistance"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float ReachedDistance = 1f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays a sprite on the item that points towards the target component.
|
/// Pinpointer arrow precision in radians.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[DataField("precision"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
[NetworkedComponent]
|
public double Precision = 0.09;
|
||||||
[Access(typeof(SharedPinpointerSystem))]
|
|
||||||
public sealed class PinpointerComponent : Component
|
|
||||||
{
|
|
||||||
// TODO: Type serializer oh god
|
|
||||||
[DataField("component")]
|
|
||||||
public string? Component;
|
|
||||||
|
|
||||||
[DataField("mediumDistance")]
|
[ViewVariables]
|
||||||
public float MediumDistance = 16f;
|
public EntityUid? Target = null;
|
||||||
|
|
||||||
[DataField("closeDistance")]
|
[ViewVariables]
|
||||||
public float CloseDistance = 8f;
|
[AutoNetworkedField]
|
||||||
|
public bool IsActive = false;
|
||||||
|
|
||||||
[DataField("reachedDistance")]
|
[ViewVariables]
|
||||||
public float ReachedDistance = 1f;
|
[AutoNetworkedField]
|
||||||
|
public Angle ArrowAngle;
|
||||||
|
|
||||||
/// <summary>
|
[ViewVariables]
|
||||||
/// Pinpointer arrow precision in radians.
|
[AutoNetworkedField]
|
||||||
/// </summary>
|
public Distance DistanceToTarget = Distance.Unknown;
|
||||||
[DataField("precision")]
|
|
||||||
public double Precision = 0.09;
|
|
||||||
|
|
||||||
public EntityUid? Target = null;
|
[ViewVariables]
|
||||||
public bool IsActive = false;
|
public bool HasTarget => DistanceToTarget != Distance.Unknown;
|
||||||
public Angle ArrowAngle;
|
}
|
||||||
public Distance DistanceToTarget = Distance.Unknown;
|
|
||||||
public bool HasTarget => DistanceToTarget != Distance.Unknown;
|
[Serializable, NetSerializable]
|
||||||
}
|
public enum Distance : byte
|
||||||
|
{
|
||||||
[Serializable, NetSerializable]
|
Unknown,
|
||||||
public sealed class PinpointerComponentState : ComponentState
|
Reached,
|
||||||
{
|
Close,
|
||||||
public bool IsActive;
|
Medium,
|
||||||
public Angle ArrowAngle;
|
Far
|
||||||
public Distance DistanceToTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum Distance : byte
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
Reached,
|
|
||||||
Close,
|
|
||||||
Medium,
|
|
||||||
Far
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,52 @@
|
|||||||
using Robust.Shared.GameStates;
|
namespace Content.Shared.Pinpointer;
|
||||||
|
|
||||||
namespace Content.Shared.Pinpointer
|
public abstract class SharedPinpointerSystem : EntitySystem
|
||||||
{
|
{
|
||||||
public abstract class SharedPinpointerSystem : EntitySystem
|
/// <summary>
|
||||||
|
/// Manually set distance from pinpointer to target
|
||||||
|
/// </summary>
|
||||||
|
public void SetDistance(EntityUid uid, Distance distance, PinpointerComponent? pinpointer = null)
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
if (!Resolve(uid, ref pinpointer))
|
||||||
{
|
return;
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<PinpointerComponent, ComponentGetState>(GetCompState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetCompState(EntityUid uid, PinpointerComponent pinpointer, ref ComponentGetState args)
|
if (distance == pinpointer.DistanceToTarget)
|
||||||
{
|
return;
|
||||||
args.State = new PinpointerComponentState
|
|
||||||
{
|
|
||||||
IsActive = pinpointer.IsActive,
|
|
||||||
ArrowAngle = pinpointer.ArrowAngle,
|
|
||||||
DistanceToTarget = pinpointer.DistanceToTarget
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
pinpointer.DistanceToTarget = distance;
|
||||||
/// Manually set distance from pinpointer to target
|
Dirty(pinpointer);
|
||||||
/// </summary>
|
}
|
||||||
public void SetDistance(EntityUid uid, Distance distance, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (distance == pinpointer.DistanceToTarget)
|
/// <summary>
|
||||||
return;
|
/// Try to manually set pinpointer arrow direction.
|
||||||
|
/// If difference between current angle and new angle is smaller than
|
||||||
|
/// pinpointer precision, new value will be ignored and it will return false.
|
||||||
|
/// </summary>
|
||||||
|
public bool TrySetArrowAngle(EntityUid uid, Angle arrowAngle, PinpointerComponent? pinpointer = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref pinpointer))
|
||||||
|
return false;
|
||||||
|
|
||||||
pinpointer.DistanceToTarget = distance;
|
if (pinpointer.ArrowAngle.EqualsApprox(arrowAngle, pinpointer.Precision))
|
||||||
Dirty(pinpointer);
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
pinpointer.ArrowAngle = arrowAngle;
|
||||||
/// Try to manually set pinpointer arrow direction.
|
Dirty(pinpointer);
|
||||||
/// If difference between current angle and new angle is smaller than
|
|
||||||
/// pinpointer precision, new value will be ignored and it will return false.
|
|
||||||
/// </summary>
|
|
||||||
public bool TrySetArrowAngle(EntityUid uid, Angle arrowAngle, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (pinpointer.ArrowAngle.EqualsApprox(arrowAngle, pinpointer.Precision))
|
return true;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
pinpointer.ArrowAngle = arrowAngle;
|
/// <summary>
|
||||||
Dirty(pinpointer);
|
/// Activate/deactivate pinpointer screen. If it has target it will start tracking it.
|
||||||
|
/// </summary>
|
||||||
|
public void SetActive(EntityUid uid, bool isActive, PinpointerComponent? pinpointer = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref pinpointer))
|
||||||
|
return;
|
||||||
|
if (isActive == pinpointer.IsActive)
|
||||||
|
return;
|
||||||
|
|
||||||
return true;
|
pinpointer.IsActive = isActive;
|
||||||
}
|
Dirty(pinpointer);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Activate/deactivate pinpointer screen. If it has target it will start tracking it.
|
|
||||||
/// </summary>
|
|
||||||
public void SetActive(EntityUid uid, bool isActive, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer))
|
|
||||||
return;
|
|
||||||
if (isActive == pinpointer.IsActive)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pinpointer.IsActive = isActive;
|
|
||||||
Dirty(pinpointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggle Pinpointer screen. If it has target it will start tracking it.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if pinpointer was activated, false otherwise</returns>
|
|
||||||
public bool TogglePinpointer(EntityUid uid, PinpointerComponent? pinpointer = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref pinpointer))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var isActive = !pinpointer.IsActive;
|
|
||||||
SetActive(uid, isActive, pinpointer);
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
- type: Transform
|
- type: Transform
|
||||||
noRot: True
|
noRot: True
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
netsync: false
|
|
||||||
noRot: True
|
noRot: True
|
||||||
sprite: Objects/Devices/pinpointer.rsi
|
sprite: Objects/Devices/pinpointer.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -16,10 +15,26 @@
|
|||||||
map: ["enum.PinpointerLayers.Base"]
|
map: ["enum.PinpointerLayers.Base"]
|
||||||
- state: pinonnull
|
- state: pinonnull
|
||||||
map: ["enum.PinpointerLayers.Screen"]
|
map: ["enum.PinpointerLayers.Screen"]
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
- type: Item
|
- type: Item
|
||||||
sprite: Objects/Devices/pinpointer.rsi
|
sprite: Objects/Devices/pinpointer.rsi
|
||||||
- type: Pinpointer
|
- type: Pinpointer
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.PinpointerVisuals.IsActive:
|
||||||
|
enum.PinpointerLayers.Screen:
|
||||||
|
True: { visible: True }
|
||||||
|
False: { visible: False }
|
||||||
|
enum.PinpointerVisuals.TargetDistance:
|
||||||
|
enum.PinpointerLayers.Screen:
|
||||||
|
Unknown: { state: pinonnull }
|
||||||
|
Reached: { state: pinondirect }
|
||||||
|
Close: { state: pinonclose }
|
||||||
|
Medium: { state: pinonmedium }
|
||||||
|
Far: { state: pinonfar }
|
||||||
|
- type: AnimationPlayer
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: pinpointer
|
name: pinpointer
|
||||||
|
|||||||
Reference in New Issue
Block a user