-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;
+
/// <summary>
-/// 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).
/// </summary>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState ,Access(typeof(ProximityDetectionSystem))]
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
+[Access(typeof(ProximityDetectionSystem))]
public sealed partial class ProximityDetectorComponent : Component
{
/// <summary>
- /// 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.
/// </summary>
- [DataField( required: true), AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
- public EntityWhitelist Criteria = new();
+ [DataField(required: true)]
+ public ComponentRegistry Components;
/// <summary>
- /// Found Entity
+ /// The entity that was found.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public EntityUid? TargetEnt;
+ [ViewVariables, AutoNetworkedField]
+ public EntityUid? Target;
/// <summary>
- /// Distance to Found Entity
+ /// The distance to <see cref="Target"/>.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public FixedPoint2 Distance = -1;
+ [ViewVariables, AutoNetworkedField]
+ public float Distance = float.PositiveInfinity;
/// <summary>
- /// The farthest distance to search for targets
+ /// The farthest distance to search for targets.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public FixedPoint2 Range = 10f;
+ [DataField, AutoNetworkedField]
+ public float Range = 10f;
- // TODO: use timespans not this
- public float AccumulatedFrameTime;
+ /// <summary>
+ /// How often detector updates.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan UpdateCooldown = TimeSpan.FromSeconds(1);
- [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public float UpdateRate = 0.3f;
+ /// <summary>
+ /// Next time detector updates.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextUpdate = TimeSpan.Zero;
}
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
+/// <summary>
+/// Handles generic proximity detector logic.
+/// </summary>
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<TransformComponent> _xformQuery;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<ProximityDetectorComponent, ComponentInit>(OnCompInit);
+ SubscribeLocalEvent<ProximityDetectorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ProximityDetectorComponent, ItemToggledEvent>(OnToggled);
+
+ _xformQuery = GetEntityQuery<TransformComponent>();
}
- private void OnCompInit(EntityUid uid, ProximityDetectorComponent component, ComponentInit args)
+ private void OnMapInit(Entity<ProximityDetectorComponent> 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<ProximityDetectorComponent> 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<ProximityDetectorComponent>();
- 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<ProximityDetectorComponent> ent, ref ItemToggledEvent args)
+ private void ClearTarget(Entity<ProximityDetectorComponent> 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<ProximityDetectorComponent> 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<ProximityDetectorComponent> 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<TransformComponent>();
- 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<Entity<IComponent>> foundEnts, bool tagSearchEnabled)
- {
- var validEnts = new HashSet<Entity<IComponent>>(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<TagComponent>(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));
+ }
}
}