Refactor TriggerSystem.Proximity (#17554)

This commit is contained in:
Vordenburg
2023-06-27 21:17:06 -04:00
committed by GitHub
parent 72607f3066
commit fb126d2044
4 changed files with 103 additions and 76 deletions

View File

@@ -1,4 +1,3 @@
using Content.Client.Trigger;
using Content.Shared.Trigger;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
@@ -20,7 +19,7 @@ public sealed partial class TriggerSystem
private static readonly Animation _flasherAnimation = new Animation
{
Length = TimeSpan.FromSeconds(0.3f),
Length = TimeSpan.FromSeconds(0.6f),
AnimationTracks = {
new AnimationTrackSpriteFlick
{
@@ -51,7 +50,8 @@ public sealed partial class TriggerSystem
private void OnProxAnimation(EntityUid uid, TriggerOnProximityComponent component, AnimationCompletedEvent args)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance)) return;
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
// So animation doesn't get spammed if no server state comes in.
_appearance.SetData(uid, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Inactive, appearance);
@@ -73,8 +73,15 @@ public sealed partial class TriggerSystem
if (!Resolve(uid, ref spriteComponent))
return;
TryComp<AnimationPlayerComponent>(component.Owner, out var player);
_appearance.TryGetData<ProximityTriggerVisuals>(appearance.Owner, ProximityTriggerVisualState.State, out var state, appearance);
if (!TryComp<AnimationPlayerComponent>(uid, out var player))
return;
if (!_appearance.TryGetData<ProximityTriggerVisuals>(uid, ProximityTriggerVisualState.State, out var state, appearance))
return;
if (!spriteComponent.LayerMapTryGet(ProximityTriggerVisualLayers.Base, out var layer))
// Don't do anything if the sprite doesn't have the layer.
return;
switch (state)
{
@@ -82,7 +89,7 @@ public sealed partial class TriggerSystem
// Don't interrupt the flash animation
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
_player.Stop(uid, player, AnimKey);
spriteComponent.LayerSetState(ProximityTriggerVisualLayers.Base, "on");
spriteComponent.LayerSetState(layer, "on");
break;
case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
@@ -91,7 +98,7 @@ public sealed partial class TriggerSystem
case ProximityTriggerVisuals.Off:
default:
_player.Stop(uid, player, AnimKey);
spriteComponent.LayerSetState(ProximityTriggerVisualLayers.Base, "off");
spriteComponent.LayerSetState(layer, "off");
break;
}
}

View File

@@ -1,7 +1,10 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Explosion;
using Content.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Explosion.Components
{
@@ -14,47 +17,74 @@ namespace Content.Server.Explosion.Components
{
public const string FixtureID = "trigger-on-proximity-fixture";
public readonly HashSet<PhysicsComponent> Colliding = new();
[ViewVariables]
public readonly Dictionary<EntityUid, PhysicsComponent> Colliding = new();
[DataField("shape", required: true)]
public IPhysShape Shape { get; set; } = new PhysShapeCircle(2f);
/// <summary>
/// What is the shape of the proximity fixture?
/// </summary>
[ViewVariables]
[DataField("shape")]
public IPhysShape Shape = new PhysShapeCircle(2f);
/// <summary>
/// How long the the proximity trigger animation plays for.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("animationDuration")]
public float AnimationDuration = 0.3f;
public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
/// <summary>
/// Whether the entity needs to be anchored for the proximity to work.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("requiresAnchored")]
public bool RequiresAnchored { get; set; } = true;
public bool RequiresAnchored = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled = true;
/// <summary>
/// The minimum delay between repeating triggers.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("cooldown")]
public float Cooldown { get; set; } = 5f;
public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
/// <summary>
/// How much cooldown has elapsed (if relevant).
/// When can the trigger run again?
/// </summary>
[DataField("accumulator")]
public float Accumulator = 0f;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextTrigger = TimeSpan.Zero;
/// <summary>
/// When will the visual state be updated again after activation?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextVisualUpdate = TimeSpan.Zero;
/// <summary>
/// What speed should the other object be moving at to trigger the proximity fixture?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("triggerSpeed")]
public float TriggerSpeed { get; set; } = 3.5f;
public float TriggerSpeed = 3.5f;
/// <summary>
/// If this proximity is triggered should we continually repeat it?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("repeating")]
internal bool Repeating = true;
public bool Repeating = true;
/// <summary>
/// What layer is the trigger fixture on?
/// </summary>
[ViewVariables]
[DataField("layer", customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
public readonly int Layer = (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable);
}
}

View File

@@ -1,28 +1,23 @@
using Content.Server.Explosion.Components;
using Content.Shared.Physics;
using Content.Shared.Trigger;
using Robust.Server.GameObjects;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events;
using Robust.Shared.Utility;
using Robust.Shared.Timing;
namespace Content.Server.Explosion.EntitySystems;
public sealed partial class TriggerSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
/// <summary>
/// Anything that has stuff touching it (to check speed) or is on cooldown.
/// </summary>
private HashSet<TriggerOnProximityComponent> _activeProximities = new();
private void InitializeProximity()
{
SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TriggerOnProximityComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown);
// Shouldn't need re-anchoring.
SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
@@ -37,7 +32,6 @@ public sealed partial class TriggerSystem
if (!component.Enabled)
{
_activeProximities.Remove(component);
component.Colliding.Clear();
}
// Re-check for contacts as we cleared them.
@@ -49,14 +43,13 @@ public sealed partial class TriggerSystem
private void OnProximityShutdown(EntityUid uid, TriggerOnProximityComponent component, ComponentShutdown args)
{
_activeProximities.Remove(component);
component.Colliding.Clear();
}
private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args)
{
component.Enabled = !component.RequiresAnchored ||
EntityManager.GetComponent<TransformComponent>(uid).Anchored;
Transform(uid).Anchored;
SetProximityAppearance(uid, component);
@@ -68,23 +61,29 @@ public sealed partial class TriggerSystem
component.Shape,
TriggerOnProximityComponent.FixtureID,
hard: false,
// TODO: Should probably have these settable via datafield but I'm lazy and it's a pain
collisionLayer: (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable));
collisionLayer: component.Layer);
}
private void OnUnpaused(EntityUid uid, TriggerOnProximityComponent component, ref EntityUnpausedEvent args)
{
component.NextTrigger += args.PausedTime;
component.NextVisualUpdate += args.PausedTime;
}
private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
{
if (args.OurFixture.ID != TriggerOnProximityComponent.FixtureID) return;
if (args.OurFixture.ID != TriggerOnProximityComponent.FixtureID)
return;
_activeProximities.Add(component);
component.Colliding.Add(args.OtherBody);
component.Colliding[args.OtherEntity] = args.OtherBody;
}
private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args)
{
if (args.OurFixture.ID != TriggerOnProximityComponent.FixtureID) return;
if (args.OurFixture.ID != TriggerOnProximityComponent.FixtureID)
return;
component.Colliding.Remove(args.OtherBody);
component.Colliding.Remove(args.OtherEntity);
}
private void SetProximityAppearance(EntityUid uid, TriggerOnProximityComponent component)
@@ -95,76 +94,67 @@ public sealed partial class TriggerSystem
}
}
private void Activate(TriggerOnProximityComponent component)
private void Activate(EntityUid uid, EntityUid user, TriggerOnProximityComponent component)
{
DebugTools.Assert(component.Enabled);
var curTime = _timing.CurTime;
if (!component.Repeating)
{
component.Enabled = false;
_activeProximities.Remove(component);
component.Colliding.Clear();
}
else
{
component.Accumulator += component.Cooldown;
component.NextTrigger = curTime + component.Cooldown;
}
if (EntityManager.TryGetComponent(component.Owner, out AppearanceComponent? appearance))
// Queue a visual update for when the animation is complete.
component.NextVisualUpdate = curTime + component.AnimationDuration;
if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
_appearance.SetData(appearance.Owner, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active, appearance);
_appearance.SetData(uid, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active, appearance);
}
Trigger(component.Owner);
Trigger(uid, user);
}
private void UpdateProximity(float frameTime)
private void UpdateProximity()
{
var toRemove = new RemQueue<TriggerOnProximityComponent>();
var curTime = _timing.CurTime;
foreach (var comp in _activeProximities)
var query = EntityQueryEnumerator<TriggerOnProximityComponent>();
while (query.MoveNext(out var uid, out var trigger))
{
MetaDataComponent? metadata = null;
if (curTime >= trigger.NextVisualUpdate)
{
// Update the visual state once the animation is done.
trigger.NextVisualUpdate = TimeSpan.MaxValue;
SetProximityAppearance(uid, trigger);
}
if (Deleted(comp.Owner, metadata))
{
toRemove.Add(comp);
if (!trigger.Enabled)
continue;
}
SetProximityAppearance(comp.Owner, comp);
if (Paused(comp.Owner, metadata)) continue;
comp.Accumulator -= frameTime;
if (comp.Accumulator > 0f) continue;
// Only remove it from accumulation when nothing colliding anymore.
if (!comp.Enabled || comp.Colliding.Count == 0)
{
comp.Accumulator = 0f;
toRemove.Add(comp);
if (curTime < trigger.NextTrigger)
// The trigger's on cooldown.
continue;
}
// Alright now that we have no cd check everything in range.
foreach (var colliding in comp.Colliding)
// Check for anything colliding and moving fast enough.
foreach (var (collidingUid, colliding) in trigger.Colliding)
{
if (Deleted(colliding.Owner)) continue;
if (Deleted(collidingUid))
continue;
if (colliding.LinearVelocity.Length < comp.TriggerSpeed) continue;
if (colliding.LinearVelocity.Length < trigger.TriggerSpeed)
continue;
// Trigger!
Activate(comp);
Activate(uid, collidingUid, trigger);
break;
}
}
foreach (var prox in toRemove)
{
_activeProximities.Remove(prox);
}
}
}

View File

@@ -214,7 +214,7 @@ namespace Content.Server.Explosion.EntitySystems
{
base.Update(frameTime);
UpdateProximity(frameTime);
UpdateProximity();
UpdateTimer(frameTime);
UpdateTimedCollide(frameTime);
}