From d1f5b2d1edfbcbeaf5ffba2b0c526e611a659af2 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Fri, 16 May 2025 23:13:34 +0100 Subject: [PATCH] rewrite ai detector (#3774) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../_DV/AiDetector/AiDetectorComponent.cs | 44 +++++++++++++ .../_DV/AiDetector/AiDetectorSystem.cs | 54 ++++++++++++++++ .../_DV/AiDetector/AiDetectorVisuals.cs | 10 +++ .../_DV/Physics/CollidingVisualsComponent.cs | 42 ------------ .../_DV/Physics/CollidingVisualsSystem.cs | 64 ------------------- .../Entities/Objects/Tools/ai_detector.yml | 21 +++--- 6 files changed, 117 insertions(+), 118 deletions(-) create mode 100644 Content.Server/_DV/AiDetector/AiDetectorComponent.cs create mode 100644 Content.Server/_DV/AiDetector/AiDetectorSystem.cs create mode 100644 Content.Shared/_DV/AiDetector/AiDetectorVisuals.cs delete mode 100644 Content.Shared/_DV/Physics/CollidingVisualsComponent.cs delete mode 100644 Content.Shared/_DV/Physics/CollidingVisualsSystem.cs diff --git a/Content.Server/_DV/AiDetector/AiDetectorComponent.cs b/Content.Server/_DV/AiDetector/AiDetectorComponent.cs new file mode 100644 index 0000000000..0d376a2c62 --- /dev/null +++ b/Content.Server/_DV/AiDetector/AiDetectorComponent.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server._DV.AiDetector; + +/// +/// Changes an appearance data string depending on distance to any AiDetectable entities. +/// +[RegisterComponent, Access(typeof(AiDetectorSystem))] +public sealed partial class AiDetectorComponent : Component +{ + /// + /// The string to use for appearance data when there is no AI nearby. + /// + [DataField] + public string Default = "none"; + + /// + /// Each range and state to use. + /// The first one found is used, so have the shortest range first. + /// + [DataField(required: true)] + public List Ranges = new(); + + /// + /// The state currently shown. + /// + [DataField] + public string State = string.Empty; + + /// + /// How long to wait between updates. + /// + [DataField] + public TimeSpan UpdateDelay = TimeSpan.FromSeconds(0.5); + + /// + /// When to next update state. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; +} + +[DataRecord] +public partial record struct AiDetectorRange(string State = "", float Range = 0f); diff --git a/Content.Server/_DV/AiDetector/AiDetectorSystem.cs b/Content.Server/_DV/AiDetector/AiDetectorSystem.cs new file mode 100644 index 0000000000..2454d68fb8 --- /dev/null +++ b/Content.Server/_DV/AiDetector/AiDetectorSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared._DV.AiDetector; +using Content.Shared._DV.Whitelist; +using Robust.Shared.Timing; + +namespace Content.Server._DV.AiDetector; + +public sealed class AiDetectorSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + private HashSet> _entities = new(); + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.NextUpdate > now) + return; + + comp.NextUpdate = now + comp.UpdateDelay; + + var ent = (uid, comp); + UpdateState(ent); + } + } + + private void UpdateState(Entity ent) + { + var coords = Transform(ent).Coordinates; + var state = ent.Comp.Default; + foreach (var range in ent.Comp.Ranges) + { + _entities.Clear(); + _lookup.GetEntitiesInRange(coords, range.Range, _entities); + if (_entities.Count == 0) + continue; + + state = range.State; + break; + } + + if (ent.Comp.State == state) + return; + + ent.Comp.State = state; + _appearance.SetData(ent.Owner, AiDetectorVisuals.Light, state); + } +} diff --git a/Content.Shared/_DV/AiDetector/AiDetectorVisuals.cs b/Content.Shared/_DV/AiDetector/AiDetectorVisuals.cs new file mode 100644 index 0000000000..b4497b4c07 --- /dev/null +++ b/Content.Shared/_DV/AiDetector/AiDetectorVisuals.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._DV.AiDetector; + +[Serializable, NetSerializable] +public enum AiDetectorVisuals : byte +{ + Layer, + Light +} diff --git a/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs b/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs deleted file mode 100644 index dfa1800652..0000000000 --- a/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Shared.Whitelist; -using Robust.Shared.Serialization; - -namespace Content.Shared._DV.Physics; - -/// -/// Changes an appearance data string depending on active collisions with fixtures. -/// -[RegisterComponent, Access(typeof(CollidingVisualsSystem))] -public sealed partial class CollidingVisualsComponent : Component -{ - /// - /// A whitelist entities must match to be counted for collisions. - /// - [DataField] - public EntityWhitelist? Whitelist; - - /// - /// The string to use for appearance data when no fixtures are being collided with. - /// - [DataField] - public string Default = "none"; - - /// - /// The list of fixtures to check for collisions, first one colliding is used so is most important. - /// - [DataField(required: true)] - public List Fixtures = new(); - - /// - /// Actively colliding fixtures. - /// - [DataField] - public HashSet Active = new(); -} - -[Serializable, NetSerializable] -public enum CollidingVisuals : byte -{ - Layer, - Fixture -} diff --git a/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs b/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs deleted file mode 100644 index a6b41c8476..0000000000 --- a/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Content.Shared.Whitelist; -using Robust.Shared.Physics.Events; - -namespace Content.Shared._DV.Physics; - -public sealed class CollidingVisualsSystem : EntitySystem -{ - [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnStartCollide); - SubscribeLocalEvent(OnEndCollide); - } - - private void OnStartCollide(Entity ent, ref StartCollideEvent args) - { - if (_whitelist.IsWhitelistFail(ent.Comp.Whitelist, args.OtherEntity)) - return; - - // update active fixtures and state - var state = ent.Comp.Default; - foreach (var id in ent.Comp.Fixtures) - { - if (args.OurFixtureId == id) - { - ent.Comp.Active.Add(id); - state = id; - break; - } - } - - SetState(ent, state); - } - - private void OnEndCollide(Entity ent, ref EndCollideEvent args) - { - if (_whitelist.IsWhitelistFail(ent.Comp.Whitelist, args.OtherEntity)) - return; - - ent.Comp.Active.Remove(args.OurFixtureId); - - // find the first state that is still active - var state = ent.Comp.Default; - foreach (var id in ent.Comp.Fixtures) - { - if (ent.Comp.Active.Contains(id)) - { - state = id; - break; - } - } - - SetState(ent, state); - } - - public void SetState(EntityUid uid, string state) - { - _appearance.SetData(uid, CollidingVisuals.Fixture, state); - } -} diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml index 6c3f2a04b3..b8a2c747aa 100644 --- a/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml +++ b/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml @@ -8,7 +8,7 @@ - state: icon - state: green-unlit shader: unshaded - map: [ enum.CollidingVisuals.Layer ] + map: [ enum.AiDetectorVisuals.Layer ] - type: Physics canCollide: true - type: Fixtures @@ -35,23 +35,20 @@ hard: false mask: - GhostImpassable - - type: CollisionWake # don't stop checking for AI just because this isn't moving - enabled: false - type: Appearance - type: GenericVisualizer visuals: - enum.CollidingVisuals.Fixture: - enum.CollidingVisuals.Layer: + enum.AiDetectorVisuals.Light: + enum.AiDetectorVisuals.Layer: none: { state: "green-unlit" } yellow: { state: "yellow-unlit" } red: { state: "red-unlit" } - - type: CollidingVisuals - whitelist: - components: - - AiDetectable - fixtures: - - red - - yellow + - type: AiDetector + ranges: + - state: red + range: 5 + - state: yellow + range: 12 - type: MappingCategories # don't map valid multitool by mistake categories: - Syndicate