From a1b588fec4248665953eab2c24fc3241e85e58c6 Mon Sep 17 00:00:00 2001 From: spess-empyrean Date: Wed, 15 Jan 2025 02:39:30 -0600 Subject: [PATCH 1/3] Ports "Fix animation looping bugs" from wizden. --- Content.Client/Jittering/JitteringSystem.cs | 3 +++ Content.Client/Light/EntitySystems/LightBehaviorSystem.cs | 3 +++ Content.Client/Light/EntitySystems/RotatingLightSystem.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs index aafaf318bb9..cd3e5065ffc 100644 --- a/Content.Client/Jittering/JitteringSystem.cs +++ b/Content.Client/Jittering/JitteringSystem.cs @@ -48,6 +48,9 @@ private void OnAnimationCompleted(EntityUid uid, JitteringComponent jittering, A if (args.Key != _jitterAnimationKey || jittering.LifeStage >= ComponentLifeStage.Stopping) return; + if (!args.Finished) + return; + if (TryComp(uid, out AnimationPlayerComponent? animationPlayer) && TryComp(uid, out SpriteComponent? sprite)) _animationPlayer.Play(uid, animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey); diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs index 11f69165cf6..ca19d8522c5 100644 --- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs @@ -19,6 +19,9 @@ public override void Initialize() private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent component, AnimationCompletedEvent args) { + if (!args.Finished) + return; + var container = component.Animations.FirstOrDefault(x => x.FullKey == args.Key); if (container == null) diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs index 842c13dedfe..5c2c4e4c875 100644 --- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs +++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs @@ -69,6 +69,9 @@ private void OnAfterAutoHandleState(EntityUid uid, RotatingLightComponent comp, private void OnAnimationComplete(EntityUid uid, RotatingLightComponent comp, AnimationCompletedEvent args) { + if (!args.Finished) + return; + PlayAnimation(uid, comp); } From 182820fdb2b1ab09ac72de640242a2230ce821ea Mon Sep 17 00:00:00 2001 From: spess-empyrean Date: Wed, 15 Jan 2025 02:54:47 -0600 Subject: [PATCH 2/3] Ports "Refactor LightBehaviorSystem" from wizden --- .../Components/LightBehaviourComponent.cs | 163 ++---------------- .../EntitySystems/ExpendableLightSystem.cs | 5 +- .../EntitySystems/LightBehaviorSystem.cs | 136 ++++++++++++++- Content.Client/Light/HandheldLightSystem.cs | 12 +- .../Items/VoidTorch/VoidTorchSystem.cs | 7 +- 5 files changed, 154 insertions(+), 169 deletions(-) diff --git a/Content.Client/Light/Components/LightBehaviourComponent.cs b/Content.Client/Light/Components/LightBehaviourComponent.cs index 7e8bf82a29e..bcdb890c013 100644 --- a/Content.Client/Light/Components/LightBehaviourComponent.cs +++ b/Content.Client/Light/Components/LightBehaviourComponent.cs @@ -68,7 +68,7 @@ public void UpdatePlaybackValues(Animation owner) if (MinDuration > 0) { - MaxTime = (float) _random.NextDouble() * (MaxDuration - MinDuration) + MinDuration; + MaxTime = (float)_random.NextDouble() * (MaxDuration - MinDuration) + MinDuration; } else { @@ -192,11 +192,11 @@ public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback( { if (interpolateValue < 0.5f) { - ApplyInterpolation(StartValue, EndValue, interpolateValue*2); + ApplyInterpolation(StartValue, EndValue, interpolateValue * 2); } else { - ApplyInterpolation(EndValue, StartValue, (interpolateValue-0.5f)*2); + ApplyInterpolation(EndValue, StartValue, (interpolateValue - 0.5f) * 2); } } else @@ -238,9 +238,9 @@ public sealed partial class RandomizeBehaviour : LightBehaviourAnimationTrack public override void OnInitialize() { - _randomValue1 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); - _randomValue2 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); - _randomValue3 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); + _randomValue1 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); + _randomValue2 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); + _randomValue3 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); } public override void OnStart() @@ -258,7 +258,7 @@ public override void OnStart() } _randomValue3 = _randomValue4; - _randomValue4 = (float) InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); + _randomValue4 = (float)InterpolateLinear(StartValue, EndValue, (float) _random.NextDouble()); } public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback( @@ -362,7 +362,7 @@ public sealed partial class LightBehaviourComponent : SharedLightBehaviourCompon [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IRobustRandom _random = default!; - private const string KeyPrefix = nameof(LightBehaviourComponent); + public const string KeyPrefix = nameof(LightBehaviourComponent); public sealed class AnimationContainer { @@ -387,7 +387,7 @@ public AnimationContainer(int key, Animation animation, LightBehaviourAnimationT public readonly List Animations = new(); [ViewVariables(VVAccess.ReadOnly)] - private Dictionary _originalPropertyValues = new(); + public Dictionary OriginalPropertyValues = new(); void ISerializationHooks.AfterDeserialization() { @@ -397,155 +397,12 @@ void ISerializationHooks.AfterDeserialization() { var animation = new Animation() { - AnimationTracks = {behaviour} + AnimationTracks = { behaviour } }; Animations.Add(new AnimationContainer(key, animation, behaviour)); key++; } } - - /// - /// If we disable all the light behaviours we want to be able to revert the light to its original state. - /// - private void CopyLightSettings(EntityUid uid, string property) - { - if (_entMan.TryGetComponent(uid, out PointLightComponent? light)) - { - var propertyValue = AnimationHelper.GetAnimatableProperty(light, property); - if (propertyValue != null) - { - _originalPropertyValues.Add(property, propertyValue); - } - } - else - { - Logger.Warning($"{_entMan.GetComponent(uid).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!"); - } - } - - /// - /// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries. - /// If specified light behaviours are already animating, calling this does nothing. - /// Multiple light behaviours can have the same ID. - /// - public void StartLightBehaviour(string id = "") - { - var uid = Owner; - if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation)) - { - return; - } - - var animations = _entMan.System(); - - foreach (var container in Animations) - { - if (container.LightBehaviour.ID == id || id == string.Empty) - { - if (!animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key)) - { - CopyLightSettings(uid, container.LightBehaviour.Property); - container.LightBehaviour.UpdatePlaybackValues(container.Animation); - animations.Play(uid, animation, container.Animation, KeyPrefix + container.Key); - } - } - } - } - - /// - /// If any light behaviour with the specified ID is animating, then stop it. - /// If no ID is specified then all light behaviours will be stopped. - /// Multiple light behaviours can have the same ID. - /// - /// - /// Should the behaviour(s) also be removed permanently? - /// Should the light have its original settings applied? - public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false) - { - var uid = Owner; - if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation)) - { - return; - } - - var toRemove = new List(); - var animations = _entMan.System(); - - foreach (var container in Animations) - { - if (container.LightBehaviour.ID == id || id == string.Empty) - { - if (animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key)) - { - animations.Stop(uid, animation, KeyPrefix + container.Key); - } - - if (removeBehaviour) - { - toRemove.Add(container); - } - } - } - - foreach (var container in toRemove) - { - Animations.Remove(container); - } - - if (resetToOriginalSettings && _entMan.TryGetComponent(uid, out PointLightComponent? light)) - { - foreach (var (property, value) in _originalPropertyValues) - { - AnimationHelper.SetAnimatableProperty(light, property, value); - } - } - - _originalPropertyValues.Clear(); - } - - /// - /// Checks if at least one behaviour is running. - /// - /// Whether at least one behaviour is running, false if none is. - public bool HasRunningBehaviours() - { - var uid = Owner; - if (!_entMan.TryGetComponent(uid, out AnimationPlayerComponent? animation)) - { - return false; - } - - var animations = _entMan.System(); - return Animations.Any(container => animations.HasRunningAnimation(uid, animation, KeyPrefix + container.Key)); - } - - /// - /// Add a new light behaviour to the component and start it immediately unless otherwise specified. - /// - public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true) - { - var key = 0; - - while (Animations.Any(x => x.Key == key)) - { - key++; - } - - var animation = new Animation() - { - AnimationTracks = {behaviour} - }; - - behaviour.Initialize(Owner, _random, _entMan); - - var container = new AnimationContainer(key, animation, behaviour); - Animations.Add(container); - - if (playImmediately) - { - StartLightBehaviour(behaviour.ID); - } - } } } diff --git a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs index 80774067301..4d52b0933d4 100644 --- a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs @@ -11,6 +11,7 @@ public sealed class ExpendableLightSystem : VisualizerSystem(uid, ExpendableLightVisuals.Behavior, out var lightBehaviourID, args.Component) && TryComp(uid, out var lightBehaviour)) { - lightBehaviour.StopLightBehaviour(); + _lightBehavior.StopLightBehaviour((uid, lightBehaviour)); if (!string.IsNullOrEmpty(lightBehaviourID)) { - lightBehaviour.StartLightBehaviour(lightBehaviourID); + _lightBehavior.StartLightBehaviour((uid, lightBehaviour), lightBehaviourID); } else if (TryComp(uid, out var light)) { diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs index ca19d8522c5..9301c31d3ea 100644 --- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs @@ -1,7 +1,9 @@ using System.Linq; using Content.Client.Light.Components; using Robust.Client.GameObjects; +using Robust.Client.Animations; using Robust.Shared.Random; +using Robust.Shared.Animations; namespace Content.Client.Light.EntitySystems; @@ -36,23 +38,145 @@ private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent } } - private void OnLightStartup(EntityUid uid, LightBehaviourComponent component, ComponentStartup args) + private void OnLightStartup(Entity entity, ref ComponentStartup args) { // TODO: Do NOT ensure component here. And use eventbus events instead... - EnsureComp(uid); + EnsureComp(entity); - foreach (var container in component.Animations) + foreach (var container in entity.Comp.Animations) { - container.LightBehaviour.Initialize(uid, _random, EntityManager); + container.LightBehaviour.Initialize(entity, _random, EntityManager); } // we need to initialize all behaviours before starting any - foreach (var container in component.Animations) + foreach (var container in entity.Comp.Animations) { if (container.LightBehaviour.Enabled) { - component.StartLightBehaviour(container.LightBehaviour.ID); + StartLightBehaviour(entity, container.LightBehaviour.ID); } } } + + /// + /// If we disable all the light behaviours we want to be able to revert the light to its original state. + /// + private void CopyLightSettings(Entity entity, string property) + { + if (EntityManager.TryGetComponent(entity, out PointLightComponent? light)) + { + var propertyValue = AnimationHelper.GetAnimatableProperty(light, property); + if (propertyValue != null) + { + entity.Comp.OriginalPropertyValues.Add(property, propertyValue); + } + } + else + { + Log.Warning($"{EntityManager.GetComponent(entity).EntityName} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!"); + } + } + /// + /// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries. + /// If specified light behaviours are already animating, calling this does nothing. + /// Multiple light behaviours can have the same ID. + /// + public void StartLightBehaviour(Entity entity, string id = "") + { + if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation)) + { + return; + } + foreach (var container in entity.Comp.Animations) + { + if (container.LightBehaviour.ID == id || id == string.Empty) + { + if (!_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) + { + CopyLightSettings(entity, container.LightBehaviour.Property); + container.LightBehaviour.UpdatePlaybackValues(container.Animation); + _player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key); + } + } + } + } + /// + /// If any light behaviour with the specified ID is animating, then stop it. + /// If no ID is specified then all light behaviours will be stopped. + /// Multiple light behaviours can have the same ID. + /// + /// + /// Should the behaviour(s) also be removed permanently? + /// Should the light have its original settings applied? + public void StopLightBehaviour(Entity entity, string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false) + { + if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation)) + { + return; + } + var comp = entity.Comp; + var toRemove = new List(); + foreach (var container in comp.Animations) + { + if (container.LightBehaviour.ID == id || id == string.Empty) + { + if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) + { + _player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key); + } + if (removeBehaviour) + { + toRemove.Add(container); + } + } + } + foreach (var container in toRemove) + { + comp.Animations.Remove(container); + } + if (resetToOriginalSettings && EntityManager.TryGetComponent(entity, out PointLightComponent? light)) + { + foreach (var (property, value) in comp.OriginalPropertyValues) + { + AnimationHelper.SetAnimatableProperty(light, property, value); + } + } + comp.OriginalPropertyValues.Clear(); + } + /// + /// Checks if at least one behaviour is running. + /// + /// Whether at least one behaviour is running, false if none is. + public bool HasRunningBehaviours(Entity entity) + { + //var uid = Owner; + if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation)) + { + return false; + } + return entity.Comp.Animations.Any(container => _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)); + } + /// + /// Add a new light behaviour to the component and start it immediately unless otherwise specified. + /// + public void AddNewLightBehaviour(Entity entity, LightBehaviourAnimationTrack behaviour, bool playImmediately = true) + { + var key = 0; + var comp = entity.Comp; + while (comp.Animations.Any(x => x.Key == key)) + { + key++; + } + var animation = new Animation() + { + AnimationTracks = { behaviour } + }; + behaviour.Initialize(entity.Owner, _random, EntityManager); + var container = new LightBehaviourComponent.AnimationContainer(key, animation, behaviour); + comp.Animations.Add(container); + if (playImmediately) + { + StartLightBehaviour(entity, behaviour.ID); + } + } } diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs index 7f18223811d..ddd99c7c483 100644 --- a/Content.Client/Light/HandheldLightSystem.cs +++ b/Content.Client/Light/HandheldLightSystem.cs @@ -3,15 +3,15 @@ using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Toggleable; -using Robust.Client.Animations; using Robust.Client.GameObjects; -using Robust.Shared.Animations; +using Content.Client.Light.EntitySystems; namespace Content.Client.Light; public sealed class HandheldLightSystem : SharedHandheldLightSystem { [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly LightBehaviorSystem _lightBehavior = default!; public override void Initialize() { @@ -41,9 +41,9 @@ private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component if (TryComp(uid, out var lightBehaviour)) { // Reset any running behaviour to reset the animated properties back to the original value, to avoid conflicts between resets - if (lightBehaviour.HasRunningBehaviours()) + if (_lightBehavior.HasRunningBehaviours((uid, lightBehaviour))) { - lightBehaviour.StopLightBehaviour(resetToOriginalSettings: true); + _lightBehavior.StopLightBehaviour((uid, lightBehaviour), resetToOriginalSettings: true); } if (!enabled) @@ -56,10 +56,10 @@ private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component case HandheldLightPowerStates.FullPower: break; // We just needed to reset all behaviours case HandheldLightPowerStates.LowPower: - lightBehaviour.StartLightBehaviour(component.RadiatingBehaviourId); + _lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.RadiatingBehaviourId); break; case HandheldLightPowerStates.Dying: - lightBehaviour.StartLightBehaviour(component.BlinkingBehaviourId); + _lightBehavior.StartLightBehaviour((uid, lightBehaviour), component.BlinkingBehaviourId); break; } } diff --git a/Content.Client/WhiteDream/BloodCult/Items/VoidTorch/VoidTorchSystem.cs b/Content.Client/WhiteDream/BloodCult/Items/VoidTorch/VoidTorchSystem.cs index 8b92fb7da89..3e0d1cfa161 100644 --- a/Content.Client/WhiteDream/BloodCult/Items/VoidTorch/VoidTorchSystem.cs +++ b/Content.Client/WhiteDream/BloodCult/Items/VoidTorch/VoidTorchSystem.cs @@ -1,4 +1,5 @@ using Content.Client.Light.Components; +using Content.Client.Light.EntitySystems; using Content.Shared.WhiteDream.BloodCult; using Content.Shared.WhiteDream.BloodCult.Items.VoidTorch; using Robust.Client.GameObjects; @@ -7,6 +8,8 @@ namespace Content.Client.WhiteDream.BloodCult.Items.VoidTorch; public sealed class VoidTorchSystem : VisualizerSystem { + [Dependency] private readonly LightBehaviorSystem _lightBehavior = default!; + protected override void OnAppearanceChange(EntityUid uid, VoidTorchComponent component, ref AppearanceChangeEvent args) @@ -17,7 +20,7 @@ protected override void OnAppearanceChange(EntityUid uid, || !TryComp(uid, out var lightBehaviour)) return; - lightBehaviour.StopLightBehaviour(); - lightBehaviour.StartLightBehaviour(state ? component.TurnOnLightBehaviour : component.TurnOffLightBehaviour); + _lightBehavior.StopLightBehaviour((uid, lightBehaviour)); + _lightBehavior.StartLightBehaviour((uid, lightBehaviour), state ? component.TurnOnLightBehaviour : component.TurnOffLightBehaviour); } } From 1885d33f38919c1fc6c504447affc61dc7b9b693 Mon Sep 17 00:00:00 2001 From: spess-empyrean Date: Wed, 15 Jan 2025 15:41:46 -0600 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: VMSolidus Signed-off-by: spess-empyrean --- .../EntitySystems/LightBehaviorSystem.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs index 9301c31d3ea..789e6e1d207 100644 --- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs @@ -84,20 +84,17 @@ private void CopyLightSettings(Entity entity, string pr public void StartLightBehaviour(Entity entity, string id = "") { if (!EntityManager.TryGetComponent(entity, out AnimationPlayerComponent? animation)) - { return; - } + foreach (var container in entity.Comp.Animations) { - if (container.LightBehaviour.ID == id || id == string.Empty) - { - if (!_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) - { - CopyLightSettings(entity, container.LightBehaviour.Property); - container.LightBehaviour.UpdatePlaybackValues(container.Animation); - _player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key); - } - } + if (container.LightBehaviour.ID != id || id != string.Empty + || _player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) + continue; + + CopyLightSettings(entity, container.LightBehaviour.Property); + container.LightBehaviour.UpdatePlaybackValues(container.Animation); + _player.Play(entity, container.Animation, LightBehaviourComponent.KeyPrefix + container.Key); } } /// @@ -118,17 +115,14 @@ public void StopLightBehaviour(Entity entity, string id var toRemove = new List(); foreach (var container in comp.Animations) { - if (container.LightBehaviour.ID == id || id == string.Empty) - { - if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) - { - _player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key); - } - if (removeBehaviour) - { - toRemove.Add(container); - } - } + if (container.LightBehaviour.ID != id || id != string.Empty) + continue; + + if (_player.HasRunningAnimation(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key)) + _player.Stop(entity, animation, LightBehaviourComponent.KeyPrefix + container.Key); + + if (removeBehaviour) + toRemove.Add(container); } foreach (var container in toRemove) {