+++ /dev/null
-using Content.Shared.Light;
-using JetBrains.Annotations;
-using Robust.Client.Animations;
-using Robust.Client.GameObjects;
-using Robust.Shared.Animations;
-using Robust.Shared.Audio;
-using Robust.Shared.Random;
-
-namespace Content.Client.Light.Visualizers
-{
- [UsedImplicitly]
- public sealed class PoweredLightVisualizer : AppearanceVisualizer
- {
- [DataField("minBlinkingTime")] private float _minBlinkingTime = 0.5f;
- [DataField("maxBlinkingTime")] private float _maxBlinkingTime = 2;
- [DataField("blinkingSound")] private SoundSpecifier? _blinkingSound = default;
-
- private bool _wasBlinking;
-
- private Action<string>? _blinkingCallback;
-
- [Obsolete("Subscribe to AppearanceChangeEvent instead.")]
- public override void OnChangeData(AppearanceComponent component)
- {
- base.OnChangeData(component);
-
- var entities = IoCManager.Resolve<IEntityManager>();
- if (!entities.TryGetComponent(component.Owner, out SpriteComponent? sprite)) return;
- if (!component.TryGetData(PoweredLightVisuals.BulbState, out PoweredLightState state)) return;
-
- switch (state)
- {
- case PoweredLightState.Empty:
- sprite.LayerSetState(PoweredLightLayers.Base, "empty");
- ToggleBlinkingAnimation(component, false);
- break;
- case PoweredLightState.Off:
- sprite.LayerSetState(PoweredLightLayers.Base, "off");
- ToggleBlinkingAnimation(component, false);
- break;
- case PoweredLightState.On:
- if (component.TryGetData(PoweredLightVisuals.Blinking, out bool isBlinking))
- ToggleBlinkingAnimation(component, isBlinking);
- if (!isBlinking)
- {
- sprite.LayerSetState(PoweredLightLayers.Base, "on");
- }
- break;
- case PoweredLightState.Broken:
- sprite.LayerSetState(PoweredLightLayers.Base, "broken");
- ToggleBlinkingAnimation(component, false);
- break;
- case PoweredLightState.Burned:
- sprite.LayerSetState(PoweredLightLayers.Base, "burn");
- ToggleBlinkingAnimation(component, false);
- break;
- }
- }
-
-
- private void ToggleBlinkingAnimation(AppearanceComponent component, bool isBlinking)
- {
- if (isBlinking == _wasBlinking)
- return;
- _wasBlinking = isBlinking;
-
- component.Owner.EnsureComponent(out AnimationPlayerComponent animationPlayer);
-
- if (isBlinking)
- {
- _blinkingCallback = (animName) => animationPlayer.Play(BlinkingAnimation(), "blinking");
- animationPlayer.AnimationCompleted += _blinkingCallback;
- animationPlayer.Play(BlinkingAnimation(), "blinking");
- }
- else if (animationPlayer.HasRunningAnimation("blinking"))
- {
- if (_blinkingCallback != null)
- animationPlayer.AnimationCompleted -= _blinkingCallback;
- animationPlayer.Stop("blinking");
- }
- }
-
- private Animation BlinkingAnimation()
- {
- var random = IoCManager.Resolve<IRobustRandom>();
- var randomTime = random.NextFloat() *
- (_maxBlinkingTime - _minBlinkingTime) + _minBlinkingTime;
-
- var blinkingAnim = new Animation()
- {
- Length = TimeSpan.FromSeconds(randomTime),
- AnimationTracks =
- {
- new AnimationTrackComponentProperty
- {
- ComponentType = typeof(PointLightComponent),
- InterpolationMode = AnimationInterpolationMode.Nearest,
- Property = nameof(PointLightComponent.Enabled),
- KeyFrames =
- {
- new AnimationTrackProperty.KeyFrame(false, 0),
- new AnimationTrackProperty.KeyFrame(true, 1)
- }
- },
- new AnimationTrackSpriteFlick()
- {
- LayerKey = PoweredLightLayers.Base,
- KeyFrames =
- {
- new AnimationTrackSpriteFlick.KeyFrame("off", 0),
- new AnimationTrackSpriteFlick.KeyFrame("on", 0.5f)
- }
- }
- }
- };
-
- if (_blinkingSound != null)
- {
- blinkingAnim.AnimationTracks.Add(new AnimationTrackPlaySound()
- {
- KeyFrames =
- {
- new AnimationTrackPlaySound.KeyFrame(_blinkingSound.GetSound(), 0.5f)
- }
- });
- }
-
- return blinkingAnim;
- }
- }
-}
--- /dev/null
+using Content.Shared.Light;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+using Robust.Shared.Random;
+
+namespace Content.Client.Light.Visualizers;
+
+public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLightVisualsComponent>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<PoweredLightVisualsComponent, AnimationCompletedEvent>(OnAnimationCompleted);
+ }
+
+ protected override void OnAppearanceChange(EntityUid uid, PoweredLightVisualsComponent comp, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ if (!AppearanceSystem.TryGetData<PoweredLightState>(uid, PoweredLightVisuals.BulbState, out var state, args.Component))
+ return;
+
+ if (comp.SpriteStateMap.TryGetValue(state, out var spriteState))
+ args.Sprite.LayerSetState(PoweredLightLayers.Base, spriteState);
+
+ SetBlinkingAnimation(
+ uid,
+ state == PoweredLightState.On
+ && (AppearanceSystem.TryGetData<bool>(uid, PoweredLightVisuals.Blinking, out var isBlinking, args.Component) && isBlinking),
+ comp
+ );
+ }
+
+ /// <summary>
+ /// Loops the blinking animation until the light should stop blinking.
+ /// </summary>
+ private void OnAnimationCompleted(EntityUid uid, PoweredLightVisualsComponent comp, AnimationCompletedEvent args)
+ {
+ if (args.Key != PoweredLightVisualsComponent.BlinkingAnimationKey)
+ return;
+
+ if(!comp.IsBlinking)
+ return;
+
+ AnimationSystem.Play(uid, Comp<AnimationPlayerComponent>(uid), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
+ }
+
+ /// <summary>
+ /// Sets whether or not the given light should be blinking.
+ /// Triggers or clears the blinking animation of the state changes.
+ /// </summary>
+ private void SetBlinkingAnimation(EntityUid uid, bool shouldBeBlinking, PoweredLightVisualsComponent comp)
+ {
+ if (shouldBeBlinking == comp.IsBlinking)
+ return;
+
+ comp.IsBlinking = shouldBeBlinking;
+
+ var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
+ if (shouldBeBlinking)
+ {
+ AnimationSystem.Play(uid, animationPlayer, BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
+ }
+ else if (AnimationSystem.HasRunningAnimation(uid, animationPlayer, PoweredLightVisualsComponent.BlinkingAnimationKey))
+ {
+ AnimationSystem.Stop(uid, animationPlayer, PoweredLightVisualsComponent.BlinkingAnimationKey);
+ }
+ }
+
+ /// <summary>
+ /// Generates a blinking animation.
+ /// Essentially just flashes the light off and on over a random time interval.
+ /// The resulting animation is looped indefinitely until the comp is set to stop blinking.
+ /// </summary>
+ private Animation BlinkingAnimation(PoweredLightVisualsComponent comp)
+ {
+ var randomTime = MathHelper.Lerp(comp.MinBlinkingAnimationCycleTime, comp.MaxBlinkingAnimationCycleTime, _random.NextFloat());
+ var blinkingAnim = new Animation()
+ {
+ Length = TimeSpan.FromSeconds(randomTime),
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty
+ {
+ ComponentType = typeof(PointLightComponent),
+ InterpolationMode = AnimationInterpolationMode.Nearest,
+ Property = nameof(PointLightComponent.Enabled),
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(false, 0),
+ new AnimationTrackProperty.KeyFrame(true, 1)
+ }
+ },
+ new AnimationTrackSpriteFlick()
+ {
+ LayerKey = PoweredLightLayers.Base,
+ KeyFrames =
+ {
+ new AnimationTrackSpriteFlick.KeyFrame(comp.SpriteStateMap[PoweredLightState.Off], 0),
+ new AnimationTrackSpriteFlick.KeyFrame(comp.SpriteStateMap[PoweredLightState.On], 0.5f)
+ }
+ }
+ }
+ };
+
+ if (comp.BlinkingSound != null)
+ {
+ var sound = _audio.GetSound(comp.BlinkingSound);
+ blinkingAnim.AnimationTracks.Add(new AnimationTrackPlaySound()
+ {
+ KeyFrames =
+ {
+ new AnimationTrackPlaySound.KeyFrame(sound, 0.5f)
+ }
+ });
+ }
+
+ return blinkingAnim;
+ }
+}
--- /dev/null
+using Content.Shared.Light;
+using Robust.Shared.Audio;
+
+namespace Content.Client.Light.Visualizers;
+
+[RegisterComponent]
+[Access(typeof(PoweredLightVisualizerSystem))]
+public sealed class PoweredLightVisualsComponent : Component
+{
+ /// <summary>
+ /// A map of the sprite states used by this visualizer indexed by the light state they correspond to.
+ /// </summary>
+ [DataField("spriteStateMap")]
+ [ViewVariables(VVAccess.ReadOnly)]
+ public readonly Dictionary<PoweredLightState, string> SpriteStateMap = new()
+ {
+ [PoweredLightState.Empty] = "empty",
+ [PoweredLightState.Off] = "off",
+ [PoweredLightState.On] = "on",
+ [PoweredLightState.Broken] = "broken",
+ [PoweredLightState.Burned] = "burn",
+ };
+
+ #region Blinking
+
+ /// <summary>
+ /// The id used to track the blinking animation for lights.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadOnly)]
+ public const string BlinkingAnimationKey = "poweredlight_blinking";
+
+ /// <summary>
+ /// The minimum length of the base blinking animation (one on-off-on cycle) in seconds.
+ /// </summary>
+ [DataField("minBlinkingTime")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float MinBlinkingAnimationCycleTime = 0.5f;
+
+ /// <summary>
+ /// The maximum length of the base blinking animation (one on-off-on cycle) in seconds.
+ /// </summary>
+ [DataField("maxBlinkingTime")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float MaxBlinkingAnimationCycleTime = 2;
+
+ /// <summary>
+ /// The sound that plays when the blinking animation cycles.
+ /// </summary>
+ [DataField("blinkingSound")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public SoundSpecifier? BlinkingSound = default;
+
+ /// <summary>
+ /// Whether or not this light is currently blinking.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool IsBlinking;
+
+ #endregion Blinking
+}
receiveFrequencyId: SmartLight
- type: ApcNetworkConnection
- type: Appearance
- visuals:
- - type: PoweredLightVisualizer
- blinkingSound:
- path: "/Audio/Machines/light_tube_on.ogg"
+ - type: PoweredLightVisuals
+ blinkingSound:
+ path: "/Audio/Machines/light_tube_on.ogg"
- type: SignalReceiver
inputs:
On: []
receiveFrequencyId: SmartLight
- type: ApcNetworkConnection
- type: Appearance
- visuals:
- - type: PoweredLightVisualizer
+ - type: PoweredLightVisuals
- type: SignalReceiver
inputs:
On: []
graph: LightFixture
node: groundLight
- type: Appearance
- visuals:
- - type: PoweredLightVisualizer
- blinkingSound:
- path: "/Audio/Machines/light_tube_on.ogg"
+ - type: PoweredLightVisuals
+ blinkingSound:
+ path: "/Audio/Machines/light_tube_on.ogg"
- type: SignalReceiver
inputs:
On: []