Fix pinpointer flicker (#15452)

This commit is contained in:
Slava0135
2023-05-07 11:20:43 +03:00
committed by GitHub
parent 7b5ad3ca83
commit dce7741b9d
6 changed files with 322 additions and 417 deletions

View File

@@ -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;
}
} }
} }
} }

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
} }

View File

@@ -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
}
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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