From: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Date: Sun, 11 May 2025 14:33:35 +0000 (+0300) Subject: Refactor: ProximityDetectionSystem (#35133) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=078814ce41ad555f31c4e89474490ed874385ecc;p=space-station-14.git Refactor: ProximityDetectionSystem (#35133) * Refactor: ProximityDetectionSystem * Update * Update * Update * Yikes * Update * Dirty * Update * Update * Lil cleanup * Update * Update --- diff --git a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs index bee75e948d..546532c3d4 100644 --- a/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs +++ b/Content.Shared/Beeper/Systems/ProximityBeeperSystem.cs @@ -25,14 +25,8 @@ public sealed class ProximityBeeperSystem : EntitySystem { if (!TryComp(owner, out var beeper)) return; - if (args.Target == null) - { - _beeper.SetMute(owner, true, beeper); - return; - } - _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Range, beeper); - _beeper.SetMute(owner, false, beeper); + _beeper.SetIntervalScaling(owner, args.Distance / args.Detector.Comp.Range, beeper); } private void OnNewProximityTarget(EntityUid owner, ProximityBeeperComponent proxBeeper, ref NewProximityTargetEvent args) diff --git a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs index 7e2bb4dfe6..8f8d37a27d 100644 --- a/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs +++ b/Content.Shared/ProximityDetection/Components/ProximityDetectorComponent.cs @@ -1,43 +1,51 @@ -using Content.Shared.FixedPoint; -using Content.Shared.ProximityDetection.Systems; -using Content.Shared.Whitelist; +using Content.Shared.ProximityDetection.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; 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) +/// Used to search for the closest entity with a range that matches specified requirements (tags and/or components). /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))] +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause] +[Access(typeof(ProximityDetectionSystem))] public sealed partial class ProximityDetectorComponent : Component { /// - /// The criteria used to filter entities - /// Note: RequireAll is only supported for tags, all components are required to count as a match! + /// Entities that detector will search for. /// - [DataField( required: true), AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] - public EntityWhitelist Criteria = new(); + [DataField(required: true)] + public ComponentRegistry Components; /// - /// Found Entity + /// The entity that was found. /// - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public EntityUid? TargetEnt; + [ViewVariables, AutoNetworkedField] + public EntityUid? Target; /// - /// Distance to Found Entity + /// The distance to . /// - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public FixedPoint2 Distance = -1; + [ViewVariables, AutoNetworkedField] + public float Distance = float.PositiveInfinity; /// - /// The farthest distance to search for targets + /// The farthest distance to search for targets. /// - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public FixedPoint2 Range = 10f; + [DataField, AutoNetworkedField] + public float Range = 10f; - // TODO: use timespans not this - public float AccumulatedFrameTime; + /// + /// How often detector updates. + /// + [DataField, AutoNetworkedField] + public TimeSpan UpdateCooldown = TimeSpan.FromSeconds(1); - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float UpdateRate = 0.3f; + /// + /// Next time detector updates. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan NextUpdate = TimeSpan.Zero; } diff --git a/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs b/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs index 0a0ac554bd..1c235f6ce4 100644 --- a/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs +++ b/Content.Shared/ProximityDetection/ProximityDetectionEvents.cs @@ -1,17 +1,27 @@ -using Content.Shared.FixedPoint; -using Content.Shared.ProximityDetection.Components; -using Robust.Shared.Serialization; +using Content.Shared.ProximityDetection.Components; namespace Content.Shared.ProximityDetection; +/// +/// Raised to determine if proximity sensor can detect an entity. +/// [ByRefEvent] -public record struct ProximityDetectionAttemptEvent(bool Cancel, FixedPoint2 Distance, Entity Detector); +public struct ProximityDetectionAttemptEvent(float distance, Entity detector, EntityUid target) +{ + public bool Cancelled; + public readonly float Distance = distance; + public readonly Entity Detector = detector; + public readonly EntityUid Target = target; +} +/// +/// Raised when distance from proximity sensor to the target was updated. +/// [ByRefEvent] -public record struct ProximityTargetUpdatedEvent(ProximityDetectorComponent Detector, EntityUid? Target, FixedPoint2 Distance); +public readonly record struct ProximityTargetUpdatedEvent(float Distance, Entity Detector, EntityUid? Target = null); +/// +/// Raised when proximity sensor got new target. +/// [ByRefEvent] -public record struct NewProximityTargetEvent(ProximityDetectorComponent Detector, EntityUid? Target); - - - +public readonly record struct NewProximityTargetEvent(float Distance, Entity Detector, EntityUid? Target = null); diff --git a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs index df302f9477..a00ec1d0c6 100644 --- a/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs +++ b/Content.Shared/ProximityDetection/Systems/ProximityDetectionSystem.cs @@ -1,219 +1,139 @@ using Content.Shared.Item.ItemToggle; -using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Item.ItemToggle.Components; using Content.Shared.ProximityDetection.Components; -using Content.Shared.Tag; -using Robust.Shared.Network; +using Robust.Shared.Timing; namespace Content.Shared.ProximityDetection.Systems; - -//This handles generic proximity detector logic +/// +/// Handles generic proximity detector logic. +/// public sealed class ProximityDetectionSystem : EntitySystem { - [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ItemToggleSystem _toggle = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly INetManager _net = default!; - //update is only run on the server + private EntityQuery _xformQuery; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnCompInit); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnToggled); + + _xformQuery = GetEntityQuery(); } - private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, ComponentInit args) + private void OnMapInit(Entity ent, ref MapInitEvent args) { - if (component.Criteria.RequireAll) - return; - Log.Debug("DetectorComponent only supports requireAll = false for tags. All components are required for a match!"); + var component = ent.Comp; + + component.NextUpdate = _timing.CurTime + component.UpdateCooldown; + DirtyField(ent, component, nameof(ProximityDetectorComponent.NextUpdate)); } - public override void Update(float frameTime) + private void OnToggled(Entity ent, ref ItemToggledEvent args) { - if (_net.IsClient) - return; + if (args.Activated) + UpdateTarget(ent); + else + ClearTarget(ent); + } + public override void Update(float frameTime) + { var query = EntityQueryEnumerator(); - while (query.MoveNext(out var owner, out var detector)) + + while (query.MoveNext(out var uid, out var component)) { - if (!_toggle.IsActivated(owner)) + if (component.NextUpdate > _timing.CurTime) continue; - detector.AccumulatedFrameTime += frameTime; - if (detector.AccumulatedFrameTime < detector.UpdateRate) + component.NextUpdate += component.UpdateCooldown; + DirtyField(uid, component, nameof(ProximityDetectorComponent.NextUpdate)); + + if (!_toggle.IsActivated(uid)) continue; - detector.AccumulatedFrameTime -= detector.UpdateRate; - RunUpdate_Internal(owner, detector); + UpdateTarget((uid, component)); } } - private void OnToggled(Entity ent, ref ItemToggledEvent args) + private void ClearTarget(Entity ent) { - if (args.Activated) - { - RunUpdate_Internal(ent, ent.Comp); + var component = ent.Comp; + + // Don't do anything if we have no target. + if (component.Target == null) return; - } - var noDetectEvent = new ProximityTargetUpdatedEvent(ent.Comp, Target: null, ent.Comp.Distance); - RaiseLocalEvent(ent, ref noDetectEvent); + component.Distance = float.PositiveInfinity; + DirtyField(ent, component, nameof(ProximityDetectorComponent.Distance)); - ent.Comp.AccumulatedFrameTime = 0; - Dirty(ent, ent.Comp); - } + component.Target = null; + DirtyField(ent, component, nameof(ProximityDetectorComponent.Target)); - public void ForceUpdate(EntityUid owner, ProximityDetectorComponent? detector = null) - { - if (!Resolve(owner, ref detector)) - return; - RunUpdate_Internal(owner, detector); - } + var updatedEv = new ProximityTargetUpdatedEvent(component.Distance, ent); + RaiseLocalEvent(ent, ref updatedEv); - private void ClearTarget(Entity ent) - { - var (uid, comp) = ent; - if (comp.TargetEnt == null) - return; - - comp.Distance = -1; - comp.TargetEnt = null; - var noDetectEvent = new ProximityTargetUpdatedEvent(comp, null, -1); - RaiseLocalEvent(uid, ref noDetectEvent); - var newTargetEvent = new NewProximityTargetEvent(comp, null); - RaiseLocalEvent(uid, ref newTargetEvent); - Dirty(uid, comp); + var newTargetEv = new NewProximityTargetEvent(component.Distance, ent); + RaiseLocalEvent(ent, ref newTargetEv); } - private void RunUpdate_Internal(EntityUid owner,ProximityDetectorComponent detector) + private void UpdateTarget(Entity detector) { - if (!_net.IsServer) //only run detection checks on the server! + var component = detector.Comp; + + if (!_xformQuery.TryGetComponent(detector, out var transform)) return; - if (Deleted(detector.TargetEnt)) - { - ClearTarget((owner, detector)); - } + if (Deleted(component.Target)) + ClearTarget(detector); - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(owner); - List<(EntityUid TargetEnt, float Distance)> detections = new(); + var closestDistance = float.PositiveInfinity; + EntityUid? closestUid = null; - if (detector.Criteria.Components == null) - { - Log.Error($"ProximityDetectorComponent on {ToPrettyString(owner)} must use at least 1 component as a filter in criteria!"); - throw new ArgumentException($"ProximityDetectorComponent on {ToPrettyString(owner)} must use at least 1 component as a filter in criteria!"); - } - var firstCompType = EntityManager.ComponentFactory.GetRegistration(detector.Criteria.Components[0]).Type; - var foundEnts = _entityLookup.GetEntitiesInRange(firstCompType,_transform.GetMapCoordinates(owner, xform), detector.Range.Float()); + var query = EntityManager.CompRegistryQueryEnumerator(component.Components); - var tagSearchEnabled = detector.Criteria.Tags is {Count: > 0}; + while (query.MoveNext(out var uid)) + { + if (!_xformQuery.TryGetComponent(uid, out var xForm)) + continue; - CheckForAllComponentsPresent(detector, ref foundEnts, tagSearchEnabled); + if (!transform.Coordinates.TryDistance(EntityManager, xForm.Coordinates, out var distance) || + distance > component.Range || distance >= closestDistance) + continue; - if (foundEnts.Count == 0) - { - UpdateTargetFromClosest(owner, detector, detections); - return; - } + var detectAttempt = new ProximityDetectionAttemptEvent(distance, detector, uid); + RaiseLocalEvent(detector, ref detectAttempt); - foreach (var ent in foundEnts) - { - if (tagSearchEnabled && ent.Comp is TagComponent tags && (detector.Criteria.RequireAll - ? _tagSystem.HasAllTags(tags, detector.Criteria.Tags!) - : _tagSystem.HasAnyTag(tags, detector.Criteria.Tags!))) + if (detectAttempt.Cancelled) 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); - } - private void CheckForAllComponentsPresent(ProximityDetectorComponent detector, ref HashSet> foundEnts, bool tagSearchEnabled) - { - var validEnts = new HashSet>(foundEnts.Count); - for (var i = 1; i < detector.Criteria.Components!.Length; i++) - { - validEnts.Clear(); - var compType = EntityManager.ComponentFactory.GetRegistration(detector.Criteria.Components[i]).Type; - foreach (var ent in foundEnts) - { - if (!HasComp(ent, compType)) - continue; - validEnts.Add(ent); - } - (foundEnts, validEnts) = (validEnts, foundEnts); + closestDistance = distance; + closestUid = uid; } - validEnts.Clear(); - if (tagSearchEnabled) - { - foreach (var ent in foundEnts) - { - if (!HasComp(ent)) - continue; - validEnts.Add(ent); - } - (foundEnts, validEnts) = (validEnts, foundEnts); - validEnts.Clear(); - } - } + var newDistance = component.Distance != closestDistance; + var newTarget = component.Target != closestUid; - private 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; - } - - private void UpdateTargetFromClosest(EntityUid owner, ProximityDetectorComponent detector, List<(EntityUid TargetEnt, float Distance)> detections) - { - if (detections.Count == 0) - { - ClearTarget((owner, detector)); - return; - } - var closestDistance = detections[0].Distance; - EntityUid closestEnt = default!; - foreach (var (ent,dist) in detections) + if (newDistance) { - if (dist >= closestDistance) - continue; - closestEnt = ent; - closestDistance = dist; + var updatedEv = new ProximityTargetUpdatedEvent(closestDistance, detector, closestUid); + RaiseLocalEvent(detector, ref updatedEv); + + component.Distance = closestDistance; + DirtyField(detector, component, nameof(ProximityDetectorComponent.Distance)); } - var newTarget = detector.TargetEnt != closestEnt; - var newData = newTarget || detector.Distance != closestDistance; - detector.TargetEnt = closestEnt; - detector.Distance = closestDistance; - Dirty(owner, detector); 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); - } + var newTargetEv = new NewProximityTargetEvent(closestDistance, detector, closestUid); + RaiseLocalEvent(detector, ref newTargetEv); - public void SetRange(EntityUid owner, float newRange, ProximityDetectorComponent? detector = null) - { - if (!Resolve(owner, ref detector)) - return; - detector.Range = newRange; - Dirty(owner, detector); + component.Target = closestUid; + DirtyField(detector, component, nameof(ProximityDetectorComponent.Target)); + } } } diff --git a/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml b/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml index 930b9c4926..e0ec2f9fd3 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml @@ -24,9 +24,8 @@ - type: ProximityBeeper - type: ProximityDetector range: 12 - criteria: - components: - - Spectral # reacts to AI eye, intentional + components: + - type: Spectral # reacts to AI eye, intentional - type: Beeper isMuted: true minBeepInterval: 0.25 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml index 0872f0fc3e..05ddb46165 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml @@ -48,9 +48,8 @@ - type: ProximityBeeper - type: ProximityDetector range: 20 - criteria: - components: - - Anomaly + components: + - type: Anomaly - type: Beeper isMuted: true minBeepInterval: 0.15