+++ /dev/null
-using Content.Shared.Trigger;
-using JetBrains.Annotations;
-using Robust.Client.Animations;
-using Robust.Client.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization;
-
-namespace Content.Client.Trigger
-{
- [UsedImplicitly]
- public sealed class TimerTriggerVisualizer : AppearanceVisualizer, ISerializationHooks
- {
- private const string AnimationKey = "priming_animation";
-
- [DataField("countdown_sound")]
- private SoundSpecifier? _countdownSound;
-
- private Animation PrimingAnimation = default!;
-
- void ISerializationHooks.AfterDeserialization()
- {
- PrimingAnimation = new Animation { Length = TimeSpan.MaxValue };
- {
- var flick = new AnimationTrackSpriteFlick();
- PrimingAnimation.AnimationTracks.Add(flick);
- flick.LayerKey = TriggerVisualLayers.Base;
- flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("primed", 0f));
-
- if (_countdownSound != null)
- {
- var sound = new AnimationTrackPlaySound();
- PrimingAnimation.AnimationTracks.Add(sound);
- sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_countdownSound.GetSound(), 0));
- }
- }
- }
-
- [Obsolete("Subscribe to your component being initialised instead.")]
- public override void InitializeEntity(EntityUid entity)
- {
- IoCManager.Resolve<IEntityManager>().EnsureComponent<AnimationPlayerComponent>(entity);
- }
-
- [Obsolete("Subscribe to AppearanceChangeEvent instead.")]
- public override void OnChangeData(AppearanceComponent component)
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
- var sprite = entMan.GetComponent<SpriteComponent>(component.Owner);
- var animPlayer = entMan.GetComponent<AnimationPlayerComponent>(component.Owner);
- if (!component.TryGetData(TriggerVisuals.VisualState, out TriggerVisualState state))
- {
- state = TriggerVisualState.Unprimed;
- }
-
- switch (state)
- {
- case TriggerVisualState.Primed:
- if (!animPlayer.HasRunningAnimation(AnimationKey))
- {
- animPlayer.Play(PrimingAnimation, AnimationKey);
- }
- break;
- case TriggerVisualState.Unprimed:
- sprite.LayerSetState(0, "icon");
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
- }
- public enum TriggerVisualLayers : byte
- {
- Base
- }
-}
--- /dev/null
+using Robust.Client.Animations;
+using Robust.Shared.Audio;
+
+namespace Content.Client.Trigger;
+
+[RegisterComponent]
+[Access(typeof(TimerTriggerVisualizerSystem))]
+public sealed class TimerTriggerVisualsComponent : Component
+{
+ /// <summary>
+ /// The key used to index the priming animation.
+ /// </summary>
+ [ViewVariables]
+ public const string AnimationKey = "priming_animation";
+
+ /// <summary>
+ /// The RSI state used while the device has not been primed.
+ /// </summary>
+ [DataField("unprimedSprite")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public string UnprimedSprite = "icon";
+
+ /// <summary>
+ /// The RSI state used when the device is primed.
+ /// Not VVWrite-able because it's only used at component init to construct the priming animation.
+ /// </summary>
+ [DataField("primingSprite")]
+ public string PrimingSprite = "primed";
+
+ /// <summary>
+ /// The sound played when the device is primed.
+ /// Not VVWrite-able because it's only used at component init to construct the priming animation.
+ /// </summary>
+ [DataField("primingSound")]
+ public SoundSpecifier? PrimingSound;
+
+ /// <summary>
+ /// The actual priming animation.
+ /// Constructed at component init from the sprite and sound.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public Animation PrimingAnimation = default!;
+}
--- /dev/null
+using Content.Shared.Trigger;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.Trigger;
+
+public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTriggerVisualsComponent>
+{
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<TimerTriggerVisualsComponent, ComponentInit>(OnComponentInit);
+ }
+
+ private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args)
+ {
+ comp.PrimingAnimation = new Animation {
+ Length = TimeSpan.MaxValue,
+ AnimationTracks = {
+ new AnimationTrackSpriteFlick() {
+ LayerKey = TriggerVisualLayers.Base,
+ KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.PrimingSprite, 0f) }
+ }
+ },
+ };
+
+ if (comp.PrimingSound != null)
+ {
+ comp.PrimingAnimation.AnimationTracks.Add(
+ new AnimationTrackPlaySound() {
+ KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(comp.PrimingSound), 0) }
+ }
+ );
+ }
+ }
+
+ protected override void OnAppearanceChange(EntityUid uid, TimerTriggerVisualsComponent comp, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null
+ || !TryComp<AnimationPlayerComponent>(uid, out var animPlayer))
+ return;
+
+ if (!AppearanceSystem.TryGetData<TriggerVisualState>(uid, TriggerVisuals.VisualState, out var state, args.Component))
+ state = TriggerVisualState.Unprimed;
+
+ switch (state)
+ {
+ case TriggerVisualState.Primed:
+ if (!AnimationSystem.HasRunningAnimation(uid, animPlayer, TimerTriggerVisualsComponent.AnimationKey))
+ AnimationSystem.Play(uid, animPlayer, comp.PrimingAnimation, TimerTriggerVisualsComponent.AnimationKey);
+ break;
+ case TriggerVisualState.Unprimed:
+ args.Sprite.LayerSetState(TriggerVisualLayers.Base, comp.UnprimedSprite);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
+
+public enum TriggerVisualLayers : byte
+{
+ Base
+}
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Appearance
- visuals:
- - type: TimerTriggerVisualizer
+ - type: AnimationPlayer
+ - type: TimerTriggerVisuals
- type: entity
name: explosive grenade
- type: SpawnOnTrigger
proto: GrenadeFlashEffect
- type: Appearance
- visuals:
- - type: TimerTriggerVisualizer
- countdown_sound:
- path: /Audio/Effects/countdown.ogg
+ - type: TimerTriggerVisuals
+ primingSound:
+ path: /Audio/Effects/countdown.ogg
- type: entity
id: GrenadeFlashEffect
intensitySlope: 30 #Will destroy the tile under it reliably, space 1-2 more to rods. Only does any significant damage in a 5-tile cross.
maxIntensity: 60
- type: Appearance
- visuals:
- - type: TimerTriggerVisualizer
- countdown_sound:
- path: /Audio/Effects/minibombcountdown.ogg
+ - type: TimerTriggerVisuals
+ primingSound:
+ path: /Audio/Effects/minibombcountdown.ogg
- type: entity
name: the nuclear option
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Appearance
- visuals:
- - type: TimerTriggerVisualizer
- countdown_sound:
- path: /Audio/Effects/countdown.ogg
+ - type: TimerTriggerVisuals
+ primingSound:
+ path: /Audio/Effects/countdown.ogg
- type: entity
name: modular grenade
energyConsumption: 50000
- type: DeleteOnTrigger
- type: Appearance
- visuals:
- - type: TimerTriggerVisualizer
- countdown_sound:
- path: /Audio/Effects/countdown.ogg
+ - type: TimerTriggerVisuals
+ primingSound:
+ path: /Audio/Effects/countdown.ogg