From: Jezithyr Date: Thu, 4 Jan 2024 23:56:23 +0000 (-0800) Subject: Created Proximity Detection and Beeper Systems (#23177) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=c242e05cdea4dbc3e783659391f16dedf5662397;p=space-station-14.git Created Proximity Detection and Beeper Systems (#23177) --- diff --git a/Content.Server/Pinpointer/ProximityBeeperComponent.cs b/Content.Server/Pinpointer/ProximityBeeperComponent.cs deleted file mode 100644 index 0fc73b5e79..0000000000 --- a/Content.Server/Pinpointer/ProximityBeeperComponent.cs +++ /dev/null @@ -1,54 +0,0 @@ -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 partial 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 deleted file mode 100644 index d4175a1af5..0000000000 --- a/Content.Server/Pinpointer/ProximityBeeperSystem.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Content.Server.PowerCell; -using Content.Shared.Interaction.Events; -using Content.Shared.Pinpointer; -using Content.Shared.PowerCell; -using Robust.Server.Audio; -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(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 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.MaxBeepInterval; - return; - } - - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(uid); - var compType = EntityManager.ComponentFactory.GetRegistration(component.Component).Type; - float? closestDistance = null; - foreach (var ent in _entityLookup.GetEntitiesInRange(compType, xform.MapPosition, component.MaximumDistance)) - { - var dist = (_transform.GetWorldPosition(xform, xformQuery) - _transform.GetWorldPosition(ent, 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; - if (component.NextBeepTime < _timing.CurTime) // Prevents spending time out of range accumulating a deficit which causes a series of very rapid beeps when comeing into range. - component.NextBeepTime = _timing.CurTime + 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) - _powerCell.SetPowerCellDrawEnabled(uid, true, draw); - 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); - _powerCell.SetPowerCellDrawEnabled(uid, false); - 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/Beeper/BeeperEvents.cs b/Content.Shared/Beeper/BeeperEvents.cs new file mode 100644 index 0000000000..7f08902cdb --- /dev/null +++ b/Content.Shared/Beeper/BeeperEvents.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Beeper; +[ByRefEvent] +public record struct BeepPlayedEvent(bool Muted); + diff --git a/Content.Shared/Beeper/Components/BeeperComponent.cs b/Content.Shared/Beeper/Components/BeeperComponent.cs new file mode 100644 index 0000000000..fab7465d85 --- /dev/null +++ b/Content.Shared/Beeper/Components/BeeperComponent.cs @@ -0,0 +1,66 @@ +using Content.Shared.Beeper.Systems; +using Content.Shared.FixedPoint; +using Content.Shared.ProximityDetection.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Beeper.Components; + +/// +/// This is used for an item that beeps based on +/// proximity to a specified component. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(BeeperSystem)), AutoGenerateComponentState] +public sealed partial class BeeperComponent : Component +{ + /// + /// Whether or not it's on. + /// + [DataField("enabled")] + public bool Enabled = true; + + /// + /// How much to scale the interval by (< 0 = min, > 1 = max) + /// + [DataField("intervalScaling"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public FixedPoint2 IntervalScaling = 0; + + /// + /// The maximum interval between beeps. + /// + [DataField("maxBeepInterval"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public TimeSpan MaxBeepInterval = TimeSpan.FromSeconds(1.5f); + + /// + /// The minimum interval between beeps. + /// + [DataField("minBeepInterval"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public TimeSpan MinBeepInterval = TimeSpan.FromSeconds(0.25f); + + /// + /// Interval for the next beep + /// + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Interval; + + /// + /// Time when we beeped last + /// + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan LastBeepTime; + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan NextBeep => LastBeepTime == TimeSpan.MaxValue ? TimeSpan.MaxValue : LastBeepTime + Interval; + + /// + /// Is the beep muted + /// + [DataField("muted"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public bool IsMuted = false; + + /// + /// The sound played when the locator beeps. + /// + [DataField("beepSound"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public SoundSpecifier? BeepSound; +} diff --git a/Content.Shared/Beeper/Components/ProximityBeeperComponent.cs b/Content.Shared/Beeper/Components/ProximityBeeperComponent.cs new file mode 100644 index 0000000000..f439d6c9c2 --- /dev/null +++ b/Content.Shared/Beeper/Components/ProximityBeeperComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Beeper.Components; + +[RegisterComponent] //component tag for events. If we add support for component pairs on events then this won't be needed anymore! +public sealed partial class ProximityBeeperComponent : Component +{ +} diff --git a/Content.Shared/Beeper/Systems/BeeperSystem.cs b/Content.Shared/Beeper/Systems/BeeperSystem.cs new file mode 100644 index 0000000000..c51eef4da9 --- /dev/null +++ b/Content.Shared/Beeper/Systems/BeeperSystem.cs @@ -0,0 +1,109 @@ +using Content.Shared.Beeper.Components; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Timing; + +namespace Content.Shared.Beeper.Systems; + + +//This handles generic proximity beeper logic +public sealed class BeeperSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() + { + } + + public override void Update(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var beeper)) + { + if (!beeper.Enabled) + continue; + RunUpdate_Internal(uid, beeper); + } + } + + public void SetEnable(EntityUid owner, bool isEnabled, BeeperComponent? beeper = null) + { + if (!Resolve(owner, ref beeper) || beeper.Enabled == isEnabled) + return; + beeper.Enabled = isEnabled; + + RunUpdate_Internal(owner, beeper); + Dirty(owner, beeper); + } + + public void SetIntervalScaling(EntityUid owner, BeeperComponent beeper, FixedPoint2 newScaling) + { + newScaling = FixedPoint2.Clamp(newScaling, 0, 1); + beeper.IntervalScaling = newScaling; + RunUpdate_Internal(owner, beeper); + Dirty(owner, beeper); + } + + public void SetInterval(EntityUid owner, BeeperComponent beeper, TimeSpan newInterval) + { + if (newInterval < beeper.MinBeepInterval) + newInterval = beeper.MinBeepInterval; + if (newInterval > beeper.MaxBeepInterval) + newInterval = beeper.MaxBeepInterval; + beeper.Interval = newInterval; + RunUpdate_Internal(owner, beeper); + Dirty(owner, beeper); + } + + public void SetIntervalScaling(EntityUid owner, FixedPoint2 newScaling, BeeperComponent? beeper = null) + { + if (!Resolve(owner, ref beeper)) + return; + SetIntervalScaling(owner, beeper, newScaling); + } + + public void SetMute(EntityUid owner, bool isMuted, BeeperComponent? comp = null) + { + if (!Resolve(owner, ref comp)) + return; + comp.IsMuted = isMuted; + } + + private void UpdateBeepInterval(EntityUid owner, BeeperComponent beeper) + { + var scalingFactor = beeper.IntervalScaling.Float(); + var interval = (beeper.MaxBeepInterval - beeper.MinBeepInterval) * scalingFactor + beeper.MinBeepInterval; + if (beeper.Interval == interval) + return; + beeper.Interval = interval; + Dirty(owner, beeper); + } + + public void ForceUpdate(EntityUid owner, BeeperComponent? beeper = null) + { + if (!Resolve(owner, ref beeper)) + return; + RunUpdate_Internal(owner, beeper); + } + + private void RunUpdate_Internal(EntityUid owner, BeeperComponent beeper) + { + if (!beeper.Enabled) + { + return; + } + UpdateBeepInterval(owner, beeper); + if (beeper.NextBeep >= _timing.CurTime) + return; + var beepEvent = new BeepPlayedEvent(beeper.IsMuted); + RaiseLocalEvent(owner, ref beepEvent); + if (!beeper.IsMuted && _net.IsServer) + { + _audio.PlayPvs(beeper.BeepSound, owner); + } + beeper.LastBeepTime = _timing.CurTime; + } +} diff --git a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs new file mode 100644 index 0000000000..bd857d4c29 --- /dev/null +++ b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs @@ -0,0 +1,114 @@ +using Content.Shared.Beeper.Components; +using Content.Shared.Interaction.Events; +using Content.Shared.Pinpointer; +using Content.Shared.PowerCell; +using Content.Shared.ProximityDetection; +using Content.Shared.ProximityDetection.Components; +using Content.Shared.ProximityDetection.Systems; + +namespace Content.Shared.Beeper.Systems; + +/// +/// This handles logic for implementing proximity beeper as a handheld tool /> +/// +public sealed class ProximityBeeperSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedPowerCellSystem _powerCell = default!; + [Dependency] private readonly ProximityDetectionSystem _proximity = default!; + [Dependency] private readonly BeeperSystem _beeper = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnPowerCellSlotEmpty); + SubscribeLocalEvent(OnNewProximityTarget); + SubscribeLocalEvent(OnProximityTargetUpdate); + } + + private void OnProximityTargetUpdate(EntityUid owner, ProximityBeeperComponent proxBeeper, ref ProximityTargetUpdatedEvent args) + { + if (!TryComp(owner, out var beeper)) + return; + if (args.Target == null) + { + _beeper.SetEnable(owner, false, beeper); + return; + } + _beeper.SetIntervalScaling(owner,args.Distance/args.Detector.Range, beeper); + _beeper.SetEnable(owner, true, beeper); + } + + private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args) + { + _beeper.SetEnable(owner, args.Target != null); + } + + private void OnUseInHand(EntityUid uid, ProximityBeeperComponent proxBeeper, UseInHandEvent args) + { + if (args.Handled) + return; + args.Handled = TryToggle(uid, proxBeeper, user: args.User); + } + + private void OnPowerCellSlotEmpty(EntityUid uid, ProximityBeeperComponent beeper, ref PowerCellSlotEmptyEvent args) + { + if (_proximity.GetEnable(uid)) + TryDisable(uid); + } + public bool TryEnable(EntityUid owner, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, + PowerCellDrawComponent? draw = null,EntityUid? user = null) + { + if (!Resolve(owner, ref beeper, ref detector)) + return false; + if (Resolve(owner, ref draw, false) && !_powerCell.HasActivatableCharge(owner, battery: draw, user: user)) + return false; + Enable(owner, beeper, detector, draw); + return true; + } + private void Enable(EntityUid owner, BeeperComponent beeper, + ProximityDetectorComponent detector, PowerCellDrawComponent? draw) + { + _proximity.SetEnable(owner, true, detector); + _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, true); + _powerCell.SetPowerCellDrawEnabled(owner, true, draw); + } + + + /// + /// Disables the proximity beeper + /// + public bool TryDisable(EntityUid owner,BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, PowerCellDrawComponent? draw = null) + { + if (!Resolve(owner, ref beeper, ref detector)) + return false; + + if (!detector.Enabled) + return false; + Disable(owner, beeper, detector, draw); + return true; + } + private void Disable(EntityUid owner, BeeperComponent beeper, + ProximityDetectorComponent detector, PowerCellDrawComponent? draw) + { + _proximity.SetEnable(owner, false, detector); + _appearance.SetData(owner, ProximityBeeperVisuals.Enabled, false); + _beeper.SetEnable(owner, false, beeper); + _powerCell.SetPowerCellDrawEnabled(owner, false, draw); + } + + /// + /// toggles the proximity beeper + /// + public bool TryToggle(EntityUid owner, ProximityBeeperComponent? proxBeeper = null, BeeperComponent? beeper = null, ProximityDetectorComponent? detector = null, + PowerCellDrawComponent? draw = null, EntityUid? user = null) + { + if (!Resolve(owner, ref proxBeeper, ref beeper, ref detector)) + return false; + + return detector.Enabled + ? TryDisable(owner, beeper, detector, draw) + : TryEnable(owner, beeper, detector, draw,user); + } +} diff --git a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs new file mode 100644 index 0000000000..7a885ff19a --- /dev/null +++ b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs @@ -0,0 +1,45 @@ +using Content.Shared.FixedPoint; +using Content.Shared.ProximityDetection.Systems; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.ProximityDetection.Components; +/// +/// This is used to search for the closest entity with a range that matches specified requirements (tags and/or components) +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))] +public sealed partial class ProximityDetectorComponent : Component +{ + /// + /// Whether or not it's on. + /// + [DataField("enabled"), AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public bool Enabled = true; + + [DataField("criteria", required: true), AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist Criteria = default!; + + /// + /// Found Entity + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public EntityUid? TargetEnt; + + /// + /// Distance to Found Entity + /// + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public FixedPoint2 Distance = -1; + + + /// + /// The farthest distance to search for targets + /// + [DataField("range"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public FixedPoint2 Range = 10f; + + public float AccumulatedFrameTime; + + [DataField("updateRate"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public float UpdateRate = 0.3f; +} diff --git a/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs b/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs new file mode 100644 index 0000000000..0a0ac554bd --- /dev/null +++ b/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs @@ -0,0 +1,17 @@ +using Content.Shared.FixedPoint; +using Content.Shared.ProximityDetection.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.ProximityDetection; + +[ByRefEvent] +public record struct ProximityDetectionAttemptEvent(bool Cancel, FixedPoint2 Distance, Entity Detector); + +[ByRefEvent] +public record struct ProximityTargetUpdatedEvent(ProximityDetectorComponent Detector, EntityUid? Target, FixedPoint2 Distance); + +[ByRefEvent] +public record struct NewProximityTargetEvent(ProximityDetectorComponent Detector, EntityUid? Target); + + + diff --git a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs new file mode 100644 index 0000000000..4b255e4a5c --- /dev/null +++ b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs @@ -0,0 +1,160 @@ +using Content.Shared.ProximityDetection.Components; +using Robust.Shared.Network; + +namespace Content.Shared.ProximityDetection.Systems; + + +//This handles generic proximity detector logic +public sealed class ProximityDetectionSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly INetManager _net = default!; + + //update is only run on the server + + public override void Initialize() + { + SubscribeLocalEvent(OnPaused); + SubscribeLocalEvent(OnUnpaused); + } + + protected void OnPaused(EntityUid owner, ProximityDetectorComponent component, EntityPausedEvent args) + { + SetEnable_Internal(owner,component,false); + } + + protected void OnUnpaused(EntityUid owner, ProximityDetectorComponent detector, ref EntityUnpausedEvent args) + { + SetEnable_Internal(owner, detector,true); + } + protected internal void SetEnable(EntityUid owner, bool enabled, ProximityDetectorComponent? detector = null) + { + if (!Resolve(owner, ref detector) || detector.Enabled == enabled) + return; + SetEnable_Internal(owner ,detector, enabled); + } + + public override void Update(float frameTime) + { + if (_net.IsClient) + return; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var owner, out var detector)) + { + if (!detector.Enabled) + continue; + detector.AccumulatedFrameTime += frameTime; + if (detector.AccumulatedFrameTime < detector.UpdateRate) + continue; + detector.AccumulatedFrameTime -= detector.UpdateRate; + RunUpdate_Internal(owner, detector); + } + } + + protected internal bool GetEnable(EntityUid owner, ProximityDetectorComponent? detector = null) + { + return Resolve(owner, ref detector, false) && detector.Enabled; + } + + protected void SetEnable_Internal(EntityUid owner,ProximityDetectorComponent detector, bool enabled) + { + detector.Enabled = enabled; + var noDetectEvent = new ProximityTargetUpdatedEvent(detector, detector.TargetEnt, detector.Distance); + RaiseLocalEvent(owner, ref noDetectEvent); + if (!enabled) + { + detector.AccumulatedFrameTime = 0; + RunUpdate_Internal(owner, detector); + Dirty(owner, detector); + return; + } + RunUpdate_Internal(owner, detector); + } + + protected void ForceUpdate(EntityUid owner, ProximityDetectorComponent? detector = null) + { + if (!Resolve(owner, ref detector)) + return; + RunUpdate_Internal(owner, detector); + } + + + protected void RunUpdate_Internal(EntityUid owner,ProximityDetectorComponent detector) + { + if (!_net.IsServer) //only run detection checks on the server! + return; + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(owner); + List<(EntityUid TargetEnt, float Distance)> detections = new(); + foreach (var ent in _entityLookup.GetEntitiesInRange(_transform.GetMapCoordinates(owner, xform), + detector.Range.Float())) + { + if (!detector.Criteria.IsValid(ent, EntityManager)) + continue; + var distance = (_transform.GetWorldPosition(xform, xformQuery) - _transform.GetWorldPosition(ent, xformQuery)).Length(); + if (CheckDetectConditions(ent, distance, owner, detector)) + { + detections.Add((ent, distance)); + } + } + UpdateTargetFromClosest(owner, detector, detections); + } + + protected bool CheckDetectConditions(EntityUid targetEntity, float dist, EntityUid owner, ProximityDetectorComponent detector) + { + var detectAttempt = new ProximityDetectionAttemptEvent(false, dist, (owner, detector)); + RaiseLocalEvent(targetEntity, ref detectAttempt); + return !detectAttempt.Cancel; + } + + protected void UpdateTargetFromClosest(EntityUid owner, ProximityDetectorComponent detector, List<(EntityUid TargetEnt, float Distance)> detections) + { + if (detections.Count == 0) + { + if (detector.TargetEnt == null) + return; + detector.Distance = -1; + detector.TargetEnt = null; + var noDetectEvent = new ProximityTargetUpdatedEvent(detector, null, -1); + RaiseLocalEvent(owner, ref noDetectEvent); + var newTargetEvent = new NewProximityTargetEvent(detector, null); + RaiseLocalEvent(owner, ref newTargetEvent); + Dirty(owner, detector); + return; + } + var closestDistance = detections[0].Distance; + EntityUid closestEnt = default!; + foreach (var (ent,dist) in detections) + { + if (dist >= closestDistance) + continue; + closestEnt = ent; + closestDistance = dist; + } + + var newTarget = detector.TargetEnt != closestEnt; + var newData = newTarget || detector.Distance != closestDistance; + detector.TargetEnt = closestEnt; + detector.Distance = closestDistance; + if (newTarget) + { + var newTargetEvent = new NewProximityTargetEvent(detector, closestEnt); + RaiseLocalEvent(owner, ref newTargetEvent); + } + + if (!newData) + return; + var targetUpdatedEvent = new ProximityTargetUpdatedEvent(detector, closestEnt, closestDistance); + RaiseLocalEvent(owner, ref targetUpdatedEvent); + Dirty(owner, detector); + } + + public void SetRange(EntityUid owner, float newRange, ProximityDetectorComponent? detector = null) + { + if (!Resolve(owner, ref detector)) + return; + detector.Range = newRange; + Dirty(owner, detector); + } +} diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml index 048bad8e5b..b02c7109d3 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml @@ -45,8 +45,14 @@ True: { visible: true } False: { visible: false } - type: ProximityBeeper - component: Anomaly - maximumDistance: 20 + - type: ProximityDetector + enabled: false + range: 20 + criteria: + components: + - Anomaly + - type: Beeper + enabled: false minBeepInterval: 0.15 maxBeepInterval: 1.00 beepSound: @@ -83,11 +89,11 @@ components: - type: Sprite sprite: Objects/Specific/Research/anomalylocatorwide.rsi - - type: ProximityBeeper - maximumDistance: 40 + - type: Beeper minBeepInterval: 0.5 maxBeepInterval: 0.5 - + - type: ProximityDetector + range: 40 - type: entity id: AnomalyLocatorWide parent: [ AnomalyLocatorWideUnpowered, PowerCellSlotSmallItem ]