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 0000000000..ae37c1e0ca Binary files /dev/null and b/Resources/Audio/Items/locator_beep.ogg differ 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 0000000000..1ded1594d4 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/icon.png differ 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 0000000000..84f9ca5cc6 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-left.png differ 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 0000000000..8c4743df71 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/meta.json b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/meta.json new file mode 100644 index 0000000000..c10f31b7e4 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by EmoGarbage404 (github) for Space Station 14.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "screen", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/screen.png b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/screen.png new file mode 100644 index 0000000000..7506f353bb Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalylocator.rsi/screen.png differ