diff --git a/Content.Client/Pinpointer/ClientPinpointerSystem.cs b/Content.Client/Pinpointer/ClientPinpointerSystem.cs new file mode 100644 index 0000000000..646247959d --- /dev/null +++ b/Content.Client/Pinpointer/ClientPinpointerSystem.cs @@ -0,0 +1,88 @@ +using Content.Shared.Pinpointer; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.IoC; +using Robust.Shared.Maths; + +namespace Content.Client.Pinpointer +{ + public sealed class ClientPinpointerSystem : SharedPinpointerSystem + { + [Dependency] private readonly IEyeManager _eyeManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandleCompState); + } + + 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 uid in ActivePinpointers) + { + UpdateEyeDir(uid); + } + } + + private void HandleCompState(EntityUid uid, PinpointerComponent pinpointer, ref ComponentHandleState args) + { + if (args.Current is not PinpointerComponentState state) return; + SetActive(uid, state.IsActive, pinpointer); + SetDirection(uid, state.DirectionToTarget, pinpointer); + SetDistance(uid, state.DistanceToTarget, pinpointer); + + UpdateAppearance(uid, pinpointer); + UpdateEyeDir(uid, pinpointer); + } + + private void UpdateAppearance(EntityUid uid, PinpointerComponent? pinpointer = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref pinpointer, ref appearance)) + return; + + appearance.SetData(PinpointerVisuals.IsActive, pinpointer.IsActive); + appearance.SetData(PinpointerVisuals.TargetDistance, pinpointer.DistanceToTarget); + } + + private void UpdateDirAppearance(EntityUid uid, Direction dir,PinpointerComponent? pinpointer = null, + AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref pinpointer, ref appearance)) + return; + + appearance.SetData(PinpointerVisuals.TargetDirection, dir); + } + + /// + /// Transform pinpointer arrow from world space to eye space + /// And send it to the appearance component + /// + private void UpdateEyeDir(EntityUid uid, PinpointerComponent? pinpointer = null) + { + if (!Resolve(uid, ref pinpointer)) + return; + + var worldDir = pinpointer.DirectionToTarget; + if (worldDir == Direction.Invalid) + { + UpdateDirAppearance(uid, Direction.Invalid, pinpointer); + return; + } + + var eye = _eyeManager.CurrentEye; + var angle = worldDir.ToAngle() + eye.Rotation; + var eyeDir = angle.GetDir(); + UpdateDirAppearance(uid, eyeDir, pinpointer); + } + } +} diff --git a/Content.Client/Pinpointer/PinpointerVisualizer.cs b/Content.Client/Pinpointer/PinpointerVisualizer.cs new file mode 100644 index 0000000000..28d3d918ee --- /dev/null +++ b/Content.Client/Pinpointer/PinpointerVisualizer.cs @@ -0,0 +1,60 @@ +using Content.Shared.Pinpointer; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.Maths; + +namespace Content.Client.Pinpointer +{ + [UsedImplicitly] + public class PinpointerVisualizer : AppearanceVisualizer + { + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out var sprite)) + return; + + // check if pinpointer screen is active + if (!component.TryGetData(PinpointerVisuals.IsActive, out bool isActive) || !isActive) + { + sprite.LayerSetVisible(PinpointerLayers.Screen, false); + return; + } + + // check if it has direction to target + sprite.LayerSetVisible(PinpointerLayers.Screen, true); + sprite.LayerSetRotation(PinpointerLayers.Screen, Angle.Zero); + + if (!component.TryGetData(PinpointerVisuals.TargetDirection, out Direction dir) || dir == Direction.Invalid) + { + sprite.LayerSetState(PinpointerLayers.Screen, "pinonnull"); + return; + } + + // check distance to target + if (!component.TryGetData(PinpointerVisuals.TargetDistance, out Distance dis)) + dis = Distance.UNKNOWN; + + switch (dis) + { + case Distance.REACHED: + sprite.LayerSetState(PinpointerLayers.Screen, "pinondirect"); + break; + case Distance.CLOSE: + sprite.LayerSetState(PinpointerLayers.Screen, "pinonclose"); + sprite.LayerSetRotation(PinpointerLayers.Screen, dir.ToAngle()); + break; + case Distance.MEDIUM: + sprite.LayerSetState(PinpointerLayers.Screen, "pinonmedium"); + sprite.LayerSetRotation(PinpointerLayers.Screen, dir.ToAngle()); + break; + case Distance.FAR: + case Distance.UNKNOWN: + sprite.LayerSetState(PinpointerLayers.Screen, "pinonfar"); + sprite.LayerSetRotation(PinpointerLayers.Screen, dir.ToAngle()); + break; + } + } + } +} diff --git a/Content.Server/Pinpointer/ServerPinpointerSystem.cs b/Content.Server/Pinpointer/ServerPinpointerSystem.cs new file mode 100644 index 0000000000..a51348e9d9 --- /dev/null +++ b/Content.Server/Pinpointer/ServerPinpointerSystem.cs @@ -0,0 +1,158 @@ +using Content.Shared.Interaction; +using Content.Shared.Pinpointer; +using Content.Shared.Whitelist; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using System.Collections.Generic; +using System.Linq; + +namespace Content.Server.Pinpointer +{ + public sealed class ServerPinpointerSystem : SharedPinpointerSystem + { + [Dependency] private readonly IEntityLookup _entityLookup = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUseInHand); + } + + private void OnUseInHand(EntityUid uid, PinpointerComponent component, UseInHandEvent args) + { + TogglePinpointer(uid, component); + + // try to find target from whitelist + if (component.IsActive && component.Whitelist != null) + { + var target = FindTargetFromWhitelist(uid, component.Whitelist); + 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? FindTargetFromWhitelist(EntityUid uid, EntityWhitelist whitelist, + ITransformComponent? transform = null) + { + if (!Resolve(uid, ref transform)) + return null; + + var mapId = transform.MapID; + var ents = _entityLookup.GetEntitiesInMap(mapId); + + // sort all entities in distance increasing order + var l = new SortedList(); + foreach (var e in ents) + { + if (whitelist.IsValid(e)) + { + var dist = (e.Transform.WorldPosition - transform.WorldPosition).LengthSquared; + l.TryAdd(dist, e.Uid); + } + } + + // 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) + { + // check if entities have transform component + if (!EntityManager.TryGetComponent(pinUid, out ITransformComponent? pin)) + return null; + if (!EntityManager.TryGetComponent(trgUid, out ITransformComponent? trg)) + return null; + + // check if they are on same map + if (pin.MapID != trg.MapID) + return null; + + // get world direction vector + var dir = (trg.WorldPosition - pin.WorldPosition); + 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; + } + } +} diff --git a/Content.Shared/Pinpointer/PinpointerComponent.cs b/Content.Shared/Pinpointer/PinpointerComponent.cs new file mode 100644 index 0000000000..ae107fac65 --- /dev/null +++ b/Content.Shared/Pinpointer/PinpointerComponent.cs @@ -0,0 +1,54 @@ +using System; +using Content.Shared.Whitelist; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Pinpointer +{ + [RegisterComponent] + [NetworkedComponent] + [Friend(typeof(SharedPinpointerSystem))] + public class PinpointerComponent : Component + { + public override string Name => "Pinpointer"; + + [DataField("whitelist")] + public EntityWhitelist? Whitelist; + + [DataField("mediumDistance")] + public float MediumDistance = 16f; + + [DataField("closeDistance")] + public float CloseDistance = 8f; + + [DataField("reachedDistance")] + public float ReachedDistance = 1f; + + public EntityUid? Target = null; + public bool IsActive = false; + public Direction DirectionToTarget = Direction.Invalid; + public Distance DistanceToTarget = Distance.UNKNOWN; + } + + [Serializable, NetSerializable] + public sealed class PinpointerComponentState : ComponentState + { + public bool IsActive; + public Direction DirectionToTarget; + public Distance DistanceToTarget; + } + + [Serializable, NetSerializable] + public enum Distance : byte + { + UNKNOWN, + REACHED, + CLOSE, + MEDIUM, + FAR + } +} diff --git a/Content.Shared/Pinpointer/PinpointerVisuals.cs b/Content.Shared/Pinpointer/PinpointerVisuals.cs new file mode 100644 index 0000000000..46ee4a286e --- /dev/null +++ b/Content.Shared/Pinpointer/PinpointerVisuals.cs @@ -0,0 +1,19 @@ +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.Pinpointer +{ + [Serializable, NetSerializable] + public enum PinpointerVisuals : byte + { + IsActive, + TargetDirection, + TargetDistance + } + + public enum PinpointerLayers : byte + { + Base, + Screen + } +} diff --git a/Content.Shared/Pinpointer/SharedPinpointerSystem.cs b/Content.Shared/Pinpointer/SharedPinpointerSystem.cs new file mode 100644 index 0000000000..ea553098ef --- /dev/null +++ b/Content.Shared/Pinpointer/SharedPinpointerSystem.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Maths; + +namespace Content.Shared.Pinpointer +{ + public abstract class SharedPinpointerSystem : EntitySystem + { + protected readonly HashSet ActivePinpointers = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(GetCompState); + } + + private void GetCompState(EntityUid uid, PinpointerComponent pinpointer, ref ComponentGetState args) + { + args.State = new PinpointerComponentState + { + IsActive = pinpointer.IsActive, + DirectionToTarget = pinpointer.DirectionToTarget, + DistanceToTarget = pinpointer.DistanceToTarget + }; + } + + /// + /// Manually set distance from pinpointer to target + /// + public void SetDistance(EntityUid uid, Distance distance, PinpointerComponent? pinpointer = null) + { + if (!Resolve(uid, ref pinpointer)) + return; + + if (distance == pinpointer.DistanceToTarget) + return; + + pinpointer.DistanceToTarget = distance; + pinpointer.Dirty(); + } + + /// + /// Manually set pinpointer arrow direction + /// + public void SetDirection(EntityUid uid, Direction directionToTarget, PinpointerComponent? pinpointer = null) + { + if (!Resolve(uid, ref pinpointer)) + return; + + if (directionToTarget == pinpointer.DirectionToTarget) + return; + + pinpointer.DirectionToTarget = directionToTarget; + pinpointer.Dirty(); + } + + /// + /// Activate/deactivate pinpointer screen. If it has target it will start tracking it. + /// + public void SetActive(EntityUid uid, bool isActive, PinpointerComponent? pinpointer = null) + { + if (!Resolve(uid, ref pinpointer)) + return; + if (isActive == pinpointer.IsActive) + return; + + // add-remove pinpointer from update list + if (isActive) + ActivePinpointers.Add(uid); + else + ActivePinpointers.Remove(uid); + + pinpointer.IsActive = isActive; + pinpointer.Dirty(); + } + + + /// + /// Toggle Pinpointer screen. If it has target it will start tracking it. + /// + /// True if pinpointer was activated, false otherwise + 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; + } + } +} diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index f21a34b141..3e5bb0caf7 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -35,6 +35,8 @@ contents: - id: NukeDisk prob: 1 + - id: PinpointerNuclear + prob: 1 - id: CaptainIDCard prob: 1 - id: ClothingHeadHatCaptain diff --git a/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml b/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml new file mode 100644 index 0000000000..cf2317ca04 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml @@ -0,0 +1,33 @@ +- type: entity + name: pinpointer + description: A handheld tracking device that locks onto certain signals. + parent: BaseItem + id: PinpointerBase + abstract: true + components: + - type: Transform + noRot: True + - type: Sprite + netsync: false + sprite: Objects/Devices/pinpointer.rsi + layers: + - state: pinpointer + map: ["enum.PinpointerLayers.Base"] + - state: pinonnull + map: ["enum.PinpointerLayers.Screen"] + - type: Item + sprite: Objects/Devices/pinpointer.rsi + - type: Pinpointer + - type: Appearance + visuals: + - type: PinpointerVisualizer + +- type: entity + name: pinpointer + id: PinpointerNuclear + parent: PinpointerBase + components: + - type: Pinpointer + whitelist: + tags: + - NukeDisk diff --git a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml index 62783ee50c..092c12ce9d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml @@ -4,6 +4,9 @@ id: NukeDisk description: A nuclear auth disk, capable of arming a nuke if used along with a code. Note from nanotrasen reads "THIS IS YOUR MOST IMPORTANT POSESSION, SECURE DAT FUKKEN DISK!" components: + - type: Tag + tags: + - NukeDisk - type: Sprite netsync: false sprite: Objects/Misc/nukedisk.rsi @@ -14,6 +17,17 @@ state: icon - type: entity - parent: NukeDisk + name: nuclear authentication disk + parent: BaseItem id: NukeDiskFake + suffix: Fake description: A nuclear auth disk, capable of.. WAIT THIS IS JUST PAINTED PLASTIC, FUCK- + components: + - type: Sprite + netsync: false + sprite: Objects/Misc/nukedisk.rsi + state: icon + - type: Item + size: 12 + sprite: Objects/Misc/nukedisk.rsi + state: icon diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index e7edf9fede..d7375805af 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -223,3 +223,5 @@ - type: Tag id: HideContextMenu +- type: Tag + id: NukeDisk diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-left.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-left.png new file mode 100644 index 0000000000..d364822849 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-right.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-right.png new file mode 100644 index 0000000000..8f268d6a75 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/meta.json b/Resources/Textures/Objects/Devices/pinpointer.rsi/meta.json new file mode 100644 index 0000000000..5e05af6671 --- /dev/null +++ b/Resources/Textures/Objects/Devices/pinpointer.rsi/meta.json @@ -0,0 +1,162 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d", + "states": [ + { + "name": "pinonalert", + "directions": 8, + "delays": [ + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ], + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinonalertdirect", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinonalertnull", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinonclose", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinondirect", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinondirectlarge", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinondirectsmall", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinondirectxtrlarge", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinonfar", + "delays": [ + [ + 0.6, + 0.2 + ] + ] + }, + { + "name": "pinonmedium", + "delays": [ + [ + 0.4, + 0.2 + ] + ] + }, + { + "name": "pinonnull", + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "pinpointer" + }, + { + "name": "pinpointer_crew" + }, + { + "name": "pinpointer_crewprox" + }, + { + "name": "pinpointer_syndicate" + }, + { + "name": "pinpointer_way" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalert.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalert.png new file mode 100644 index 0000000000..39e73ee266 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalert.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertdirect.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertdirect.png new file mode 100644 index 0000000000..6e53ef3a3b Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertdirect.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertnull.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertnull.png new file mode 100644 index 0000000000..1556d82c76 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonalertnull.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonclose.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonclose.png new file mode 100644 index 0000000000..d652e4cd02 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonclose.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirect.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirect.png new file mode 100644 index 0000000000..c2ac6e44e2 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirect.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectlarge.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectlarge.png new file mode 100644 index 0000000000..c8b1fa4875 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectlarge.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectsmall.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectsmall.png new file mode 100644 index 0000000000..ab7940efd9 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectsmall.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectxtrlarge.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectxtrlarge.png new file mode 100644 index 0000000000..5db693402c Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinondirectxtrlarge.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonfar.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonfar.png new file mode 100644 index 0000000000..fd0fbf51ac Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonfar.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonmedium.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonmedium.png new file mode 100644 index 0000000000..5f8e89aec2 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonmedium.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonnull.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonnull.png new file mode 100644 index 0000000000..a309d053e5 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinonnull.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer.png new file mode 100644 index 0000000000..24c7e0dc68 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crew.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crew.png new file mode 100644 index 0000000000..af24601880 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crew.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crewprox.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crewprox.png new file mode 100644 index 0000000000..c147288085 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_crewprox.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_syndicate.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_syndicate.png new file mode 100644 index 0000000000..46e9fbba6c Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_syndicate.png differ diff --git a/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_way.png b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_way.png new file mode 100644 index 0000000000..478750439f Binary files /dev/null and b/Resources/Textures/Objects/Devices/pinpointer.rsi/pinpointer_way.png differ