rewrite ai detector (#3774)
Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
parent
0286cf40c9
commit
d1f5b2d1ed
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AiDetector;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AiDetectorVisuals : byte
|
||||
{
|
||||
Layer,
|
||||
Light
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue