rewrite ai detector (#3774)

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas 2025-05-16 23:13:34 +01:00 committed by GitHub
parent 0286cf40c9
commit d1f5b2d1ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 117 additions and 118 deletions

View File

@ -0,0 +1,44 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server._DV.AiDetector;
/// <summary>
/// Changes an appearance data string depending on distance to any <c>AiDetectable</c> entities.
/// </summary>
[RegisterComponent, Access(typeof(AiDetectorSystem))]
public sealed partial class AiDetectorComponent : Component
{
/// <summary>
/// The string to use for appearance data when there is no AI nearby.
/// </summary>
[DataField]
public string Default = "none";
/// <summary>
/// Each range and state to use.
/// The first one found is used, so have the shortest range first.
/// </summary>
[DataField(required: true)]
public List<AiDetectorRange> Ranges = new();
/// <summary>
/// The state currently shown.
/// </summary>
[DataField]
public string State = string.Empty;
/// <summary>
/// How long to wait between updates.
/// </summary>
[DataField]
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(0.5);
/// <summary>
/// When to next update state.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate = TimeSpan.Zero;
}
[DataRecord]
public partial record struct AiDetectorRange(string State = "", float Range = 0f);

View File

@ -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<Entity<AiDetectableComponent>> _entities = new();
public override void Update(float frameTime)
{
base.Update(frameTime);
var now = _timing.CurTime;
var query = EntityQueryEnumerator<AiDetectorComponent>();
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<AiDetectorComponent> ent)
{
var coords = Transform(ent).Coordinates;
var state = ent.Comp.Default;
foreach (var range in ent.Comp.Ranges)
{
_entities.Clear();
_lookup.GetEntitiesInRange<AiDetectableComponent>(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);
}
}

View File

@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared._DV.AiDetector;
[Serializable, NetSerializable]
public enum AiDetectorVisuals : byte
{
Layer,
Light
}

View File

@ -1,42 +0,0 @@
using Content.Shared.Whitelist;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Physics;
/// <summary>
/// Changes an appearance data string depending on active collisions with fixtures.
/// </summary>
[RegisterComponent, Access(typeof(CollidingVisualsSystem))]
public sealed partial class CollidingVisualsComponent : Component
{
/// <summary>
/// A whitelist entities must match to be counted for collisions.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
/// <summary>
/// The string to use for appearance data when no fixtures are being collided with.
/// </summary>
[DataField]
public string Default = "none";
/// <summary>
/// The list of fixtures to check for collisions, first one colliding is used so is most important.
/// </summary>
[DataField(required: true)]
public List<string> Fixtures = new();
/// <summary>
/// Actively colliding fixtures.
/// </summary>
[DataField]
public HashSet<string> Active = new();
}
[Serializable, NetSerializable]
public enum CollidingVisuals : byte
{
Layer,
Fixture
}

View File

@ -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<CollidingVisualsComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<CollidingVisualsComponent, EndCollideEvent>(OnEndCollide);
}
private void OnStartCollide(Entity<CollidingVisualsComponent> 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<CollidingVisualsComponent> 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);
}
}

View File

@ -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