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

View File

@@ -1,7 +1,10 @@
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Shared.Explosion; using Content.Shared.Explosion;
using Content.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Explosion.Components namespace Content.Server.Explosion.Components
{ {
@@ -14,47 +17,74 @@ namespace Content.Server.Explosion.Components
{ {
public const string FixtureID = "trigger-on-proximity-fixture"; 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)] /// <summary>
public IPhysShape Shape { get; set; } = new PhysShapeCircle(2f); /// What is the shape of the proximity fixture?
/// </summary>
[ViewVariables]
[DataField("shape")]
public IPhysShape Shape = new PhysShapeCircle(2f);
/// <summary> /// <summary>
/// How long the the proximity trigger animation plays for. /// How long the the proximity trigger animation plays for.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("animationDuration")] [DataField("animationDuration")]
public float AnimationDuration = 0.3f; public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
/// <summary> /// <summary>
/// Whether the entity needs to be anchored for the proximity to work. /// Whether the entity needs to be anchored for the proximity to work.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("requiresAnchored")] [DataField("requiresAnchored")]
public bool RequiresAnchored { get; set; } = true; public bool RequiresAnchored = true;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")] [DataField("enabled")]
public bool Enabled = true; public bool Enabled = true;
/// <summary>
/// The minimum delay between repeating triggers.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("cooldown")] [DataField("cooldown")]
public float Cooldown { get; set; } = 5f; public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
/// <summary> /// <summary>
/// How much cooldown has elapsed (if relevant). /// When can the trigger run again?
/// </summary> /// </summary>
[DataField("accumulator")] [ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0f; [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> /// <summary>
/// What speed should the other object be moving at to trigger the proximity fixture? /// What speed should the other object be moving at to trigger the proximity fixture?
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("triggerSpeed")] [DataField("triggerSpeed")]
public float TriggerSpeed { get; set; } = 3.5f; public float TriggerSpeed = 3.5f;
/// <summary> /// <summary>
/// If this proximity is triggered should we continually repeat it? /// If this proximity is triggered should we continually repeat it?
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("repeating")] [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.Server.Explosion.Components;
using Content.Shared.Physics;
using Content.Shared.Trigger; using Content.Shared.Trigger;
using Robust.Server.GameObjects;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Events;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Timing;
namespace Content.Server.Explosion.EntitySystems; namespace Content.Server.Explosion.EntitySystems;
public sealed partial class TriggerSystem public sealed partial class TriggerSystem
{ {
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = 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() private void InitializeProximity()
{ {
SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide); SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide); SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TriggerOnProximityComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown); SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown);
// Shouldn't need re-anchoring. // Shouldn't need re-anchoring.
SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor); SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
@@ -37,7 +32,6 @@ public sealed partial class TriggerSystem
if (!component.Enabled) if (!component.Enabled)
{ {
_activeProximities.Remove(component);
component.Colliding.Clear(); component.Colliding.Clear();
} }
// Re-check for contacts as we cleared them. // 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) private void OnProximityShutdown(EntityUid uid, TriggerOnProximityComponent component, ComponentShutdown args)
{ {
_activeProximities.Remove(component);
component.Colliding.Clear(); component.Colliding.Clear();
} }
private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args)
{ {
component.Enabled = !component.RequiresAnchored || component.Enabled = !component.RequiresAnchored ||
EntityManager.GetComponent<TransformComponent>(uid).Anchored; Transform(uid).Anchored;
SetProximityAppearance(uid, component); SetProximityAppearance(uid, component);
@@ -68,23 +61,29 @@ public sealed partial class TriggerSystem
component.Shape, component.Shape,
TriggerOnProximityComponent.FixtureID, TriggerOnProximityComponent.FixtureID,
hard: false, hard: false,
// TODO: Should probably have these settable via datafield but I'm lazy and it's a pain collisionLayer: component.Layer);
collisionLayer: (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable)); }
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) 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[args.OtherEntity] = args.OtherBody;
component.Colliding.Add(args.OtherBody);
} }
private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args) 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) 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); DebugTools.Assert(component.Enabled);
var curTime = _timing.CurTime;
if (!component.Repeating) if (!component.Repeating)
{ {
component.Enabled = false; component.Enabled = false;
_activeProximities.Remove(component);
component.Colliding.Clear(); component.Colliding.Clear();
} }
else 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)) if (!trigger.Enabled)
{
toRemove.Add(comp);
continue; continue;
}
SetProximityAppearance(comp.Owner, comp); if (curTime < trigger.NextTrigger)
// The trigger's on cooldown.
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);
continue; continue;
}
// Alright now that we have no cd check everything in range. // Check for anything colliding and moving fast enough.
foreach (var (collidingUid, colliding) in trigger.Colliding)
foreach (var colliding in comp.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! // Trigger!
Activate(comp); Activate(uid, collidingUid, trigger);
break; break;
} }
} }
foreach (var prox in toRemove)
{
_activeProximities.Remove(prox);
}
} }
} }

View File

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