From fadee354db38d9aadbf6e350a506bcb67b28639c Mon Sep 17 00:00:00 2001
From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Date: Sun, 23 Apr 2023 01:39:33 -0400
Subject: [PATCH] Anomaly Locator (#15677)
---
.../Pinpointer/ProximityBeeperComponent.cs | 54 ++++++
.../Pinpointer/ProximityBeeperSystem.cs | 167 ++++++++++++++++++
.../Pinpointer/SharedProximityBeeper.cs | 9 +
Resources/Audio/Items/attributions.yml | 5 +
Resources/Audio/Items/locator_beep.ogg | Bin 0 -> 5858 bytes
.../Catalog/Research/technologies.yml | 1 +
.../Objects/Specific/Research/anomaly.yml | 43 +++++
.../Entities/Structures/Machines/lathe.yml | 1 +
.../Prototypes/Recipes/Lathes/devices.yml | 8 +
.../Research/anomalylocator.rsi/icon.png | Bin 0 -> 442 bytes
.../anomalylocator.rsi/inhand-left.png | Bin 0 -> 307 bytes
.../anomalylocator.rsi/inhand-right.png | Bin 0 -> 295 bytes
.../Research/anomalylocator.rsi/meta.json | 33 ++++
.../Research/anomalylocator.rsi/screen.png | Bin 0 -> 223 bytes
14 files changed, 321 insertions(+)
create mode 100644 Content.Server/Pinpointer/ProximityBeeperComponent.cs
create mode 100644 Content.Server/Pinpointer/ProximityBeeperSystem.cs
create mode 100644 Content.Shared/Pinpointer/SharedProximityBeeper.cs
create mode 100644 Resources/Audio/Items/locator_beep.ogg
create mode 100644 Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/icon.png
create mode 100644 Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-left.png
create mode 100644 Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-right.png
create mode 100644 Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/meta.json
create mode 100644 Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/screen.png
diff --git a/Content.Server/Pinpointer/ProximityBeeperComponent.cs b/Content.Server/Pinpointer/ProximityBeeperComponent.cs
new file mode 100644
index 0000000000..8abc7b6df7
--- /dev/null
+++ b/Content.Server/Pinpointer/ProximityBeeperComponent.cs
@@ -0,0 +1,54 @@
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Pinpointer;
+
+///
+/// This is used for an item that beeps based on
+/// proximity to a specified component.
+///
+[RegisterComponent, Access(typeof(ProximityBeeperSystem))]
+public sealed class ProximityBeeperComponent : Component
+{
+ ///
+ /// Whether or not it's on.
+ ///
+ [DataField("enabled")]
+ public bool Enabled;
+
+ ///
+ /// The target component that is being searched for
+ ///
+ [DataField("component", required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string Component = default!;
+
+ ///
+ /// The farthest distance a target can be for the beep to occur
+ ///
+ [DataField("maximumDistance"), ViewVariables(VVAccess.ReadWrite)]
+ public float MaximumDistance = 10f;
+
+ ///
+ /// The maximum interval between beeps.
+ ///
+ [DataField("maxBeepInterval"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan MaxBeepInterval = TimeSpan.FromSeconds(1.5f);
+
+ ///
+ /// The minimum interval between beeps.
+ ///
+ [DataField("minBeepInterval"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan MinBeepInterval = TimeSpan.FromSeconds(0.25f);
+
+ ///
+ /// When the next beep will occur
+ ///
+ [DataField("nextBeepTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextBeepTime;
+
+ ///
+ /// The sound played when the locator beeps.
+ ///
+ [DataField("beepSound")]
+ public SoundSpecifier? BeepSound;
+}
diff --git a/Content.Server/Pinpointer/ProximityBeeperSystem.cs b/Content.Server/Pinpointer/ProximityBeeperSystem.cs
new file mode 100644
index 0000000000..472a50fb23
--- /dev/null
+++ b/Content.Server/Pinpointer/ProximityBeeperSystem.cs
@@ -0,0 +1,167 @@
+using Content.Server.PowerCell;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Pinpointer;
+using Robust.Server.GameObjects;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Pinpointer;
+
+///
+/// This handles logic and interaction relating to
+///
+public sealed class ProximityBeeperSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+ [Dependency] private readonly AudioSystem _audio = default!;
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly PowerCellSystem _powerCell = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnUseInHand);
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnUnpaused);
+ SubscribeLocalEvent(OnPowerCellSlotEmpty);
+ }
+ private void OnUseInHand(EntityUid uid, ProximityBeeperComponent component, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = TryToggle(uid, component, args.User);
+ }
+
+ private void OnInit(EntityUid uid, ProximityBeeperComponent component, ComponentInit args)
+ {
+ if (component.NextBeepTime < _timing.CurTime)
+ component.NextBeepTime = _timing.CurTime;
+ }
+
+ private void OnUnpaused(EntityUid uid, ProximityBeeperComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextBeepTime += args.PausedTime;
+ }
+
+ private void OnPowerCellSlotEmpty(EntityUid uid, ProximityBeeperComponent component, ref PowerCellSlotEmptyEvent args)
+ {
+ if (component.Enabled)
+ TryDisable(uid, component);
+ }
+
+ ///
+ /// Beeps the proximitybeeper as well as sets the time for the next beep
+ /// based on proximity to entities with the target component.
+ ///
+ public void UpdateBeep(EntityUid uid, ProximityBeeperComponent? component = null, bool playBeep = true)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (!component.Enabled)
+ {
+ component.NextBeepTime += component.MinBeepInterval;
+ return;
+ }
+
+ var xformQuery = GetEntityQuery();
+ var xform = xformQuery.GetComponent(uid);
+ var comp = EntityManager.ComponentFactory.GetRegistration(component.Component).Type;
+ float? closestDistance = null;
+ foreach (var targetXform in _entityLookup.GetComponentsInRange(xform.MapPosition, component.MaximumDistance))
+ {
+ // forgive me father, for i have sinned.
+ var ent = targetXform.Owner;
+
+ if (!HasComp(ent, comp))
+ continue;
+
+ var dist = (_transform.GetWorldPosition(xform, xformQuery) - _transform.GetWorldPosition(targetXform, xformQuery)).Length;
+ if (dist >= (closestDistance ?? float.MaxValue))
+ continue;
+ closestDistance = dist;
+ }
+
+ if (closestDistance is not { } distance)
+ return;
+
+ if (playBeep)
+ _audio.PlayPvs(component.BeepSound, uid);
+
+ var scalingFactor = distance / component.MaximumDistance;
+ var interval = (component.MaxBeepInterval - component.MinBeepInterval) * scalingFactor + component.MinBeepInterval;
+ component.NextBeepTime += interval;
+ }
+
+ ///
+ /// Enables the proximity beeper
+ ///
+ public bool TryEnable(EntityUid uid, ProximityBeeperComponent? component = null, EntityUid? user = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ TryComp(uid, out var draw);
+
+ if (!_powerCell.HasActivatableCharge(uid, battery: draw, user: user))
+ return false;
+
+ component.Enabled = true;
+ _appearance.SetData(uid, ProximityBeeperVisuals.Enabled, true);
+ component.NextBeepTime = _timing.CurTime;
+ UpdateBeep(uid, component, false);
+ if (draw != null)
+ draw.Enabled = true;
+ return true;
+ }
+
+ ///
+ /// Disables the proximity beeper
+ ///
+ public bool TryDisable(EntityUid uid, ProximityBeeperComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ if (!component.Enabled)
+ return false;
+
+ component.Enabled = false;
+ _appearance.SetData(uid, ProximityBeeperVisuals.Enabled, false);
+ if (TryComp(uid, out var draw))
+ draw.Enabled = true;
+ UpdateBeep(uid, component);
+ return true;
+ }
+
+ ///
+ /// toggles the proximity beeper
+ ///
+ public bool TryToggle(EntityUid uid, ProximityBeeperComponent? component = null, EntityUid? user = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ return component.Enabled
+ ? TryDisable(uid, component)
+ : TryEnable(uid, component, user);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var beeper))
+ {
+ if (!beeper.Enabled)
+ continue;
+
+ if (_timing.CurTime < beeper.NextBeepTime)
+ continue;
+ UpdateBeep(uid, beeper);
+ }
+ }
+}
diff --git a/Content.Shared/Pinpointer/SharedProximityBeeper.cs b/Content.Shared/Pinpointer/SharedProximityBeeper.cs
new file mode 100644
index 0000000000..5163112683
--- /dev/null
+++ b/Content.Shared/Pinpointer/SharedProximityBeeper.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Pinpointer;
+
+[Serializable, NetSerializable]
+public enum ProximityBeeperVisuals : byte
+{
+ Enabled
+}
diff --git a/Resources/Audio/Items/attributions.yml b/Resources/Audio/Items/attributions.yml
index 19f084fed6..1ebbf8311f 100644
--- a/Resources/Audio/Items/attributions.yml
+++ b/Resources/Audio/Items/attributions.yml
@@ -3,6 +3,11 @@
copyright: "Created by Pól, converted to OGG and Mono by EmoGarbage"
source: "https://freesound.org/people/P%C3%B3l/sounds/385927/"
+- files: ["locator_beep.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by MATRIXXX_, converted to OGG, shortened, sped up, and pitched up by EmoGarbage404 (github)"
+ source: "https://freesound.org/people/MATRIXXX_/sounds/657947/"
+
- files: ["trayhit1.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Time immemorial"
diff --git a/Resources/Audio/Items/locator_beep.ogg b/Resources/Audio/Items/locator_beep.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..ae37c1e0ca2cca3afee769e57de345133a293a60
GIT binary patch
literal 5858
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
C8i05J
literal 0
HcmV?d00001
diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml
index 1ad09c82ba..9db86f498a 100644
--- a/Resources/Prototypes/Catalog/Research/technologies.yml
+++ b/Resources/Prototypes/Catalog/Research/technologies.yml
@@ -521,6 +521,7 @@
- ScanningModuleStockPart
- NodeScanner
- AnomalyScanner
+ - AnomalyLocator
- type: technology
name: technologies-anomaly-technology
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
index ff3c7606ad..a91be8c588 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
@@ -20,3 +20,46 @@
- type: GuideHelp
guides:
- ScannersAndVessels
+
+- type: entity
+ id: AnomalyLocator
+ parent: [ BaseItem, PowerCellSlotSmallItem ]
+ name: anomaly locator
+ description: A device designed to aid in the locating of anomalies. Did you check the gas miners?
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Research/anomalylocator.rsi
+ netsync: false
+ layers:
+ - state: icon
+ - state: screen
+ shader: unshaded
+ visible: false
+ map: ["enum.PowerDeviceVisualLayers.Powered"]
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.ProximityBeeperVisuals.Enabled:
+ enum.PowerDeviceVisualLayers.Powered:
+ True: { visible: true }
+ False: { visible: false }
+ - type: PowerCellDraw
+ drawRate: 10
+ useRate: 0
+ - type: ProximityBeeper
+ component: Anomaly
+ beepSound:
+ path: "/Audio/Items/locator_beep.ogg"
+ params:
+ maxdistance: 1
+ volume: -8
+
+- type: entity
+ id: AnomalyLocatorNoBattery
+ parent: AnomalyLocator
+ suffix: No Battery
+ components:
+ - type: ItemSlots
+ slots:
+ cell_slot:
+ name: power-cell-slot-component-slot-name-default
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 18bae43997..05ed6926d3 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -170,6 +170,7 @@
- ConveyorBeltAssembly
- AppraisalTool
- AnomalyScanner
+ - AnomalyLocator
- RCD
- RCDAmmo
- HydroponicsToolScythe
diff --git a/Resources/Prototypes/Recipes/Lathes/devices.yml b/Resources/Prototypes/Recipes/Lathes/devices.yml
index 135d1005ae..36b66a7cc4 100644
--- a/Resources/Prototypes/Recipes/Lathes/devices.yml
+++ b/Resources/Prototypes/Recipes/Lathes/devices.yml
@@ -49,6 +49,14 @@
Plastic: 200
Glass: 100
+- type: latheRecipe
+ id: AnomalyLocator
+ result: AnomalyLocatorNoBattery
+ completetime: 3
+ materials:
+ Steel: 400
+ Glass: 100
+
- type: latheRecipe
id: AnomalyScanner
result: AnomalyScanner
diff --git a/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/icon.png b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..1ded1594d4c7aa5dec8cc0eed408ab9763bb77cb
GIT binary patch
literal 442
zcmV;r0Y(0aP)Px$bV)=(R9J=WmA`AkKorNnkq(`bMY1{yBG}QbP-#0j1Lq>~c0Lhk(0ZE#H%%
zp1=%3zM-UYu6NA>lYlG-kyaJhwzCl1n?SjIwKi9+-tJBy&>O@0G9gJ42q7zZNs?e0
zCIX*h|21g@b^(>Z&t$UI`vA&UFpY^2k3%3g1c?)MCEt3jL%a-OdiViY{%TVH36)gN
zO{>b<{6oEg#^asux=i1Icl-H?Z&jd}&j_7%7XZ+#)Lta5#JB7jyiE+=EJVivR!s07*qoM6N<$g32z!)&Kwi
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-left.png
new file mode 100644
index 0000000000000000000000000000000000000000..84f9ca5cc6e1440cf8f16a3d007c26ff1f1785a1
GIT binary patch
literal 307
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|-g>$?hE&XX
zd&`>Zkb_9;!^KN{XNz`DGddDtmL$BPTvV)SZg0eml{eUq^4`qU?F-d9=su^L?O#5l
z`a?gpv$jG&BY?&$=$-wWVDWALxeC=y9Hp7>+zXcl|JL%X+d0EzRmvBm%cU3s;4s>sB1&%K}aEa|xN>KNnwSAic^PE+3=`Ci3y_uD<6jALe1d%jBbYyTS<
z7Vl^PG`VD((az16Z=U*7FW^#{ReF*YY$=HN7qjAB!_BHYK|34Y-M9Kx#<5kVL1dlv
vhx@<3sb^i6-4IkJlyy;dgHt(3@%-J)1#3gwPfxE+193fF{an^LB{Ts5S4Mz=
literal 0
HcmV?d00001
diff --git a/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c4743df715b2e9340e99c8a8e4d823b59fa7bab
GIT binary patch
literal 295
zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|9(uYshE&XX
zd&`io$w8p?;qrw`zd9|Nc1&X`@4}BPbDCSu&Ew-;o4)DrtsP=*N8DUel~@12p0}xT
zwq!<{3Q#`-LxXkToEb-plK0JhEStNz+E?6XS#mGmye(5Szu$J;F8H?d(UCR!#7^GBG-v!aGl~*F^EdkAyo@zjcb!%jEQ}32)vC9`x;%W!q${z{Oiow-
zx9@m#WJzS6dh&hw_+nLcux%h>&f`yfjaN3vUpS^+{9|9$4*3gVw=HYp_r@A#7gva1
nDXy5l|8M`_ua6lS7(OtsJIWf#`}@1b0+3EmS3j3^P6*Upi@`lKfN>izSXi!>%n+|uhZnmIWwbTiw9E6GdRZDv))
zteW5d=MWQ6GYITUy8NH<>Qf2l+pbk79@o{@{kKoZ^`7(G+!iF;pn30vt`#>=-lU5l
NuBWS?%Q~loCIG_FR7L;*
literal 0
HcmV?d00001