diff --git a/Content.Client/Explosion/TriggerSystem.Proximity.cs b/Content.Client/Explosion/TriggerSystem.Proximity.cs index 7c98e1374d..41e4f914ff 100644 --- a/Content.Client/Explosion/TriggerSystem.Proximity.cs +++ b/Content.Client/Explosion/TriggerSystem.Proximity.cs @@ -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(uid, out var appearance)) return; + if (!TryComp(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(component.Owner, out var player); - _appearance.TryGetData(appearance.Owner, ProximityTriggerVisualState.State, out var state, appearance); + if (!TryComp(uid, out var player)) + return; + + if (!_appearance.TryGetData(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; } } diff --git a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs b/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs index 97fff3d664..720aec0a02 100644 --- a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs +++ b/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs @@ -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 { @@ -12,49 +15,76 @@ namespace Content.Server.Explosion.Components [RegisterComponent] public sealed class TriggerOnProximityComponent : SharedTriggerOnProximityComponent { - public const string FixtureID = "trigger-on-proximity-fixture"; + public const string FixtureID = "trigger-on-proximity-fixture"; - public readonly HashSet Colliding = new(); + [ViewVariables] + public readonly Dictionary Colliding = new(); - [DataField("shape", required: true)] - public IPhysShape Shape { get; set; } = new PhysShapeCircle(2f); + /// + /// What is the shape of the proximity fixture? + /// + [ViewVariables] + [DataField("shape")] + public IPhysShape Shape = new PhysShapeCircle(2f); /// /// How long the the proximity trigger animation plays for. /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("animationDuration")] - public float AnimationDuration = 0.3f; + public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f); /// /// Whether the entity needs to be anchored for the proximity to work. /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("requiresAnchored")] - public bool RequiresAnchored { get; set; } = true; + public bool RequiresAnchored = true; + [ViewVariables(VVAccess.ReadWrite)] [DataField("enabled")] public bool Enabled = true; + /// + /// The minimum delay between repeating triggers. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("cooldown")] - public float Cooldown { get; set; } = 5f; + public TimeSpan Cooldown = TimeSpan.FromSeconds(5); /// - /// How much cooldown has elapsed (if relevant). + /// When can the trigger run again? /// - [DataField("accumulator")] - public float Accumulator = 0f; + [ViewVariables(VVAccess.ReadWrite)] + [DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextTrigger = TimeSpan.Zero; + + /// + /// When will the visual state be updated again after activation? + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextVisualUpdate = TimeSpan.Zero; /// /// What speed should the other object be moving at to trigger the proximity fixture? /// [ViewVariables(VVAccess.ReadWrite)] [DataField("triggerSpeed")] - public float TriggerSpeed { get; set; } = 3.5f; + public float TriggerSpeed = 3.5f; /// /// If this proximity is triggered should we continually repeat it? /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("repeating")] - internal bool Repeating = true; + public bool Repeating = true; + + /// + /// What layer is the trigger fixture on? + /// + [ViewVariables] + [DataField("layer", customTypeSerializer: typeof(FlagSerializer))] + public readonly int Layer = (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable); } } diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs index 1a616d3c0b..d082b17d99 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs @@ -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!; - /// - /// Anything that has stuff touching it (to check speed) or is on cooldown. - /// - private HashSet _activeProximities = new(); - private void InitializeProximity() { SubscribeLocalEvent(OnProximityStartCollide); SubscribeLocalEvent(OnProximityEndCollide); SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnProximityShutdown); // Shouldn't need re-anchoring. SubscribeLocalEvent(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(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(); + var curTime = _timing.CurTime; - foreach (var comp in _activeProximities) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var trigger)) { - MetaDataComponent? metadata = null; - - if (Deleted(comp.Owner, metadata)) + if (curTime >= trigger.NextVisualUpdate) { - toRemove.Add(comp); - continue; + // Update the visual state once the animation is done. + trigger.NextVisualUpdate = TimeSpan.MaxValue; + SetProximityAppearance(uid, trigger); } - 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 (!trigger.Enabled) continue; - } - // Alright now that we have no cd check everything in range. + if (curTime < trigger.NextTrigger) + // The trigger's on cooldown. + continue; - 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); - } } } diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index f61cbee54c..ec1d42493b 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -214,7 +214,7 @@ namespace Content.Server.Explosion.EntitySystems { base.Update(frameTime); - UpdateProximity(frameTime); + UpdateProximity(); UpdateTimer(frameTime); UpdateTimedCollide(frameTime); }