]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Resolves AirlockVisualizer is Obsolete (#13884)
authorTemporalOroboros <TemporalOroboros@gmail.com>
Sat, 22 Apr 2023 09:18:16 +0000 (02:18 -0700)
committerGitHub <noreply@github.com>
Sat, 22 Apr 2023 09:18:16 +0000 (19:18 +1000)
16 files changed:
Content.Client/Doors/AirlockSystem.cs
Content.Client/Doors/AirlockVisualizer.cs [deleted file]
Content.Client/Doors/DoorSystem.cs
Content.Shared/Doors/Components/AirlockComponent.cs
Content.Shared/Doors/Components/DoorComponent.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Resources/Prototypes/Entities/Structures/Decoration/curtains.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml
Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml
Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml
Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml
Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml
Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml
Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml

index 1898edb62185ed3fc9ab1edb16db6bf6050aaa43..cf4c3dfca0d0903341670440dab1fba081d2e910 100644 (file)
@@ -1,5 +1,112 @@
+using Content.Client.Wires.Visualizers;
+using Content.Shared.Doors.Components;
 using Content.Shared.Doors.Systems;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
 
 namespace Content.Client.Doors;
 
-public sealed class AirlockSystem : SharedAirlockSystem { }
+public sealed class AirlockSystem : SharedAirlockSystem
+{
+    [Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<AirlockComponent, ComponentStartup>(OnComponentStartup);
+        SubscribeLocalEvent<AirlockComponent, AppearanceChangeEvent>(OnAppearanceChange);
+    }
+
+    private void OnComponentStartup(EntityUid uid, AirlockComponent comp, ComponentStartup args)
+    {
+        // Has to be on component startup because we don't know what order components initialize in and running this before DoorComponent inits _will_ crash.
+        if(!TryComp<DoorComponent>(uid, out var door))
+            return;
+
+        if (comp.OpenUnlitVisible) // Otherwise there are flashes of the fallback sprite between clicking on the door and the door closing animation starting.
+        {
+            door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
+            door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));
+        }
+
+        ((Animation)door.OpeningAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
+            {
+                LayerKey = DoorVisualLayers.BaseUnlit,
+                KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f) },
+            }
+        );
+
+        ((Animation)door.ClosingAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
+            {
+                LayerKey = DoorVisualLayers.BaseUnlit,
+                KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f) },
+            }
+        );
+
+        door.DenyingAnimation = new Animation()
+        {
+            Length = TimeSpan.FromSeconds(comp.DenyAnimationTime),
+            AnimationTracks =
+            {
+                new AnimationTrackSpriteFlick()
+                {
+                    LayerKey = DoorVisualLayers.BaseUnlit,
+                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.DenySpriteState, 0f) },
+                }
+            }
+        };
+
+        if(!comp.AnimatePanel)
+            return;
+
+        ((Animation)door.OpeningAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
+        {
+            LayerKey = WiresVisualLayers.MaintenancePanel,
+            KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningPanelSpriteState, 0f)},
+        });
+
+        ((Animation)door.ClosingAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick
+        {
+            LayerKey = WiresVisualLayers.MaintenancePanel,
+            KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingPanelSpriteState, 0f)},
+        });
+    }
+
+    private void OnAppearanceChange(EntityUid uid, AirlockComponent comp, ref AppearanceChangeEvent args)
+    {
+        if (args.Sprite == null)
+            return;
+
+        var boltedVisible = false;
+        var emergencyLightsVisible = false;
+        var unlitVisible = false;
+
+        if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
+            state = DoorState.Closed;
+
+        if (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
+        {
+            boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
+            emergencyLightsVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.EmergencyLights, out var eaLights, args.Component) && eaLights;
+            unlitVisible =
+                    state == DoorState.Closing
+                ||  state == DoorState.Opening
+                ||  state == DoorState.Denying
+                || (state == DoorState.Open && comp.OpenUnlitVisible)
+                || (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
+        }
+
+        args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
+        args.Sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible);
+        if (comp.EmergencyAccessLayer)
+        {
+            args.Sprite.LayerSetVisible(
+                DoorVisualLayers.BaseEmergencyAccess,
+                    emergencyLightsVisible
+                &&  state != DoorState.Open
+                &&  state != DoorState.Opening
+                &&  state != DoorState.Closing
+            );
+        }
+    }
+}
diff --git a/Content.Client/Doors/AirlockVisualizer.cs b/Content.Client/Doors/AirlockVisualizer.cs
deleted file mode 100644 (file)
index c4214aa..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-using System;
-using Content.Client.Wires.Visualizers;
-using Content.Shared.Doors.Components;
-using JetBrains.Annotations;
-using Robust.Client.Animations;
-using Robust.Client.GameObjects;
-using Robust.Client.ResourceManagement;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.Manager.Attributes;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Doors
-{
-    [UsedImplicitly]
-    public sealed class AirlockVisualizer : AppearanceVisualizer, ISerializationHooks
-    {
-        [Dependency] private readonly IEntityManager _entMan = default!;
-        [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly IResourceCache _resourceCache = default!;
-
-        private const string AnimationKey = "airlock_animation";
-
-        [DataField("animationTime")]
-        private float _delay = 0.8f;
-
-        [DataField("denyAnimationTime")]
-        private float _denyDelay = 0.3f;
-
-
-        [DataField("emagAnimationTime")]
-        private float _delayEmag = 1.5f;
-
-        /// <summary>
-        ///     Whether the maintenance panel is animated or stays static.
-        ///     False for windoors.
-        /// </summary>
-        [DataField("animatedPanel")]
-        private bool _animatedPanel = true;
-
-        /// <summary>
-        /// Means the door is simply open / closed / opening / closing. No wires or access.
-        /// </summary>
-        [DataField("simpleVisuals")]
-        private bool _simpleVisuals = false;
-
-        /// <summary>
-        ///     Whether the BaseUnlit layer should still be visible when the airlock
-        ///     is opened.
-        /// </summary>
-        [DataField("openUnlitVisible")]
-        private bool _openUnlitVisible = false;
-
-        /// <summary>
-        ///     Whether the door should have an emergency access layer
-        /// </summary>
-        [DataField("emergencyAccessLayer")]
-        private bool _emergencyAccessLayer = true;
-
-        private Animation CloseAnimation = default!;
-        private Animation OpenAnimation = default!;
-        private Animation DenyAnimation = default!;
-        private Animation EmaggingAnimation = default!;
-
-        void ISerializationHooks.AfterDeserialization()
-        {
-            IoCManager.InjectDependencies(this);
-
-            CloseAnimation = new Animation { Length = TimeSpan.FromSeconds(_delay) };
-            {
-                var flick = new AnimationTrackSpriteFlick();
-                CloseAnimation.AnimationTracks.Add(flick);
-                flick.LayerKey = DoorVisualLayers.Base;
-                flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing", 0f));
-
-                if (!_simpleVisuals)
-                {
-                    var flickUnlit = new AnimationTrackSpriteFlick();
-                    CloseAnimation.AnimationTracks.Add(flickUnlit);
-                    flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
-                    flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("closing_unlit", 0f));
-
-                    if (_animatedPanel)
-                    {
-                        var flickMaintenancePanel = new AnimationTrackSpriteFlick();
-                        CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
-                        flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
-                        flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
-                    }
-                }
-            }
-
-            OpenAnimation = new Animation { Length = TimeSpan.FromSeconds(_delay) };
-            {
-                var flick = new AnimationTrackSpriteFlick();
-                OpenAnimation.AnimationTracks.Add(flick);
-                flick.LayerKey = DoorVisualLayers.Base;
-                flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening", 0f));
-
-                if (!_simpleVisuals)
-                {
-                    var flickUnlit = new AnimationTrackSpriteFlick();
-                    OpenAnimation.AnimationTracks.Add(flickUnlit);
-                    flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
-                    flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("opening_unlit", 0f));
-
-                    if (_animatedPanel)
-                    {
-                        var flickMaintenancePanel = new AnimationTrackSpriteFlick();
-                        OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
-                        flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
-                        flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
-                    }
-                }
-            }
-            EmaggingAnimation = new Animation { Length = TimeSpan.FromSeconds(_delay) };
-            {
-                var flickUnlit = new AnimationTrackSpriteFlick();
-                EmaggingAnimation.AnimationTracks.Add(flickUnlit);
-                flickUnlit.LayerKey = DoorVisualLayers.BaseUnlit;
-                flickUnlit.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("sparks", 0f));
-            }
-
-            if (!_simpleVisuals)
-            {
-                DenyAnimation = new Animation { Length = TimeSpan.FromSeconds(_denyDelay) };
-                {
-                    var flick = new AnimationTrackSpriteFlick();
-                    DenyAnimation.AnimationTracks.Add(flick);
-                    flick.LayerKey = DoorVisualLayers.BaseUnlit;
-                    flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny_unlit", 0f));
-                }
-            }
-        }
-
-        [Obsolete("Subscribe to your component being initialised instead.")]
-        public override void InitializeEntity(EntityUid entity)
-        {
-            if (!_entMan.HasComponent<AnimationPlayerComponent>(entity))
-            {
-                _entMan.AddComponent<AnimationPlayerComponent>(entity);
-            }
-        }
-
-        [Obsolete("Subscribe to AppearanceChangeEvent instead.")]
-        public override void OnChangeData(AppearanceComponent component)
-        {
-            // only start playing animations once.
-            if (!_gameTiming.IsFirstTimePredicted)
-                return;
-
-            base.OnChangeData(component);
-
-            var sprite = _entMan.GetComponent<SpriteComponent>(component.Owner);
-            var animPlayer = _entMan.GetComponent<AnimationPlayerComponent>(component.Owner);
-            if (!component.TryGetData(DoorVisuals.State, out DoorState state))
-            {
-                state = DoorState.Closed;
-            }
-
-            var door = _entMan.GetComponent<DoorComponent>(component.Owner);
-
-            if (component.TryGetData(DoorVisuals.BaseRSI, out string baseRsi))
-            {
-                if (!_resourceCache.TryGetResource<RSIResource>(SharedSpriteComponent.TextureRoot / baseRsi, out var res))
-                {
-                    Logger.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
-                }
-                foreach (ISpriteLayer layer in sprite.AllLayers)
-                {
-                    layer.Rsi = res?.RSI;
-                }
-            }
-
-            if (animPlayer.HasRunningAnimation(AnimationKey))
-            {
-                animPlayer.Stop(AnimationKey);
-            }
-            switch (state)
-            {
-                case DoorState.Open:
-                    sprite.LayerSetState(DoorVisualLayers.Base, "open");
-                    if (_openUnlitVisible && !_simpleVisuals)
-                    {
-                        sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit");
-                    }
-                    break;
-                case DoorState.Closed:
-                    sprite.LayerSetState(DoorVisualLayers.Base, "closed");
-                    if (!_simpleVisuals)
-                    {
-                        sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "closed_unlit");
-                        sprite.LayerSetState(DoorVisualLayers.BaseBolted, "bolted_unlit");
-                    }
-                    break;
-                case DoorState.Opening:
-                    animPlayer.Play(OpenAnimation, AnimationKey);
-                    break;
-                case DoorState.Closing:
-                    if (door.CurrentlyCrushing.Count == 0)
-                        animPlayer.Play(CloseAnimation, AnimationKey);
-                    else
-                        sprite.LayerSetState(DoorVisualLayers.Base, "closed");
-                    break;
-                case DoorState.Denying:
-                    animPlayer.Play(DenyAnimation, AnimationKey);
-                    break;
-                case DoorState.Welded:
-                    break;
-                case DoorState.Emagging:
-                    animPlayer.Play(EmaggingAnimation, AnimationKey);
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException();
-            }
-
-            if (_simpleVisuals)
-                return;
-
-            var boltedVisible = false;
-            var emergencyLightsVisible = false;
-            var unlitVisible = false;
-
-            if (component.TryGetData(DoorVisuals.Powered, out bool powered) && powered)
-            {
-                boltedVisible = component.TryGetData(DoorVisuals.BoltLights, out bool lights) && lights;
-                emergencyLightsVisible = component.TryGetData(DoorVisuals.EmergencyLights, out bool eaLights) && eaLights;
-                unlitVisible = state == DoorState.Closing
-                    || state == DoorState.Opening
-                    || state == DoorState.Denying
-                    || state == DoorState.Open && _openUnlitVisible
-                    || (component.TryGetData(DoorVisuals.ClosedLights, out bool closedLights) && closedLights);
-            }
-
-            sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
-            sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible);
-            if (_emergencyAccessLayer)
-            {
-                sprite.LayerSetVisible(DoorVisualLayers.BaseEmergencyAccess,
-                        emergencyLightsVisible
-                        && state != DoorState.Open
-                        && state != DoorState.Opening
-                        && state != DoorState.Closing);
-            }
-        }
-    }
-
-    public enum DoorVisualLayers : byte
-    {
-        Base,
-        BaseUnlit,
-        BaseBolted,
-        BaseEmergencyAccess,
-    }
-}
index ac393c519bfa1f7f07dee0fc710440289b9b4c4d..c77412ac65f5557d3f393ada106074e570c61e98 100644 (file)
 using Content.Shared.Doors.Components;
 using Content.Shared.Doors.Systems;
+using Robust.Client.Animations;
 using Robust.Client.GameObjects;
+using Robust.Client.ResourceManagement;
 using Robust.Shared.Audio;
 using Robust.Shared.GameObjects;
 using Robust.Shared.Player;
+using Robust.Shared.Timing;
 
 namespace Content.Client.Doors;
 
 public sealed class DoorSystem : SharedDoorSystem
 {
-    // Gotta love it when both the client-side and server-side sprite components both have a draw depth, but for
-    // whatever bloody reason the shared component doesn't.
-    protected override void UpdateAppearance(EntityUid uid, DoorComponent? door = null)
+    [Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly IResourceCache _resourceCache = default!;
+
+    public override void Initialize()
     {
-        if (!Resolve(uid, ref door))
+        base.Initialize();
+        SubscribeLocalEvent<DoorComponent, AppearanceChangeEvent>(OnAppearanceChange);
+    }
+
+    protected override void OnComponentInit(EntityUid uid, DoorComponent comp, ComponentInit args)
+    {
+        comp.OpenSpriteStates = new(2);
+        comp.ClosedSpriteStates = new(2);
+
+        comp.OpenSpriteStates.Add((DoorVisualLayers.Base, comp.OpenSpriteState));
+        comp.ClosedSpriteStates.Add((DoorVisualLayers.Base, comp.ClosedSpriteState));
+
+        comp.OpeningAnimation = new Animation()
+        {
+            Length = TimeSpan.FromSeconds(comp.OpeningAnimationTime),
+            AnimationTracks =
+            {
+                new AnimationTrackSpriteFlick()
+                {
+                    LayerKey = DoorVisualLayers.Base,
+                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.OpeningSpriteState, 0f) }
+                }
+            },
+        };
+
+        comp.ClosingAnimation = new Animation()
+        {
+            Length = TimeSpan.FromSeconds(comp.ClosingAnimationTime),
+            AnimationTracks =
+            {
+                new AnimationTrackSpriteFlick()
+                {
+                    LayerKey = DoorVisualLayers.Base,
+                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.ClosingSpriteState, 0f) }
+                }
+            },
+        };
+
+        comp.EmaggingAnimation = new Animation ()
+        {
+            Length = TimeSpan.FromSeconds(comp.EmaggingAnimationTime),
+            AnimationTracks =
+            {
+                new AnimationTrackSpriteFlick()
+                {
+                    LayerKey = DoorVisualLayers.BaseUnlit,
+                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.EmaggingSpriteState, 0f) }
+                }
+            },
+        };
+    }
+
+    private void OnAppearanceChange(EntityUid uid, DoorComponent comp, ref AppearanceChangeEvent args)
+    {
+        if (args.Sprite == null || !_gameTiming.IsFirstTimePredicted)
             return;
 
-        base.UpdateAppearance(uid, door);
+        if(!AppearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
+            state = DoorState.Closed;
+
+        if (AppearanceSystem.TryGetData<string>(uid, DoorVisuals.BaseRSI, out var baseRsi, args.Component))
+        {
+            if (!_resourceCache.TryGetResource<RSIResource>(SharedSpriteComponent.TextureRoot / baseRsi, out var res))
+            {
+                Logger.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
+            }
+            foreach (ISpriteLayer layer in args.Sprite.AllLayers)
+            {
+                layer.Rsi = res?.RSI;
+            }
+        }
+        
+        TryComp<AnimationPlayerComponent>(uid, out var animPlayer);
+        if (_animationSystem.HasRunningAnimation(uid, animPlayer, DoorComponent.AnimationKey))
+            _animationSystem.Stop(uid, animPlayer, DoorComponent.AnimationKey); // Halt all running anomations.
 
-        if (TryComp(uid, out SpriteComponent? sprite))
+        args.Sprite.DrawDepth = comp.ClosedDrawDepth;
+        switch(state)
         {
-            sprite.DrawDepth = (door.State == DoorState.Open)
-                ? door.OpenDrawDepth
-                : door.ClosedDrawDepth;
+            case DoorState.Open:
+                args.Sprite.DrawDepth = comp.OpenDrawDepth;
+                foreach(var (layer, layerState) in comp.OpenSpriteStates)
+                {
+                    args.Sprite.LayerSetState(layer, layerState);
+                }
+                break;
+            case DoorState.Closed:
+                foreach(var (layer, layerState) in comp.ClosedSpriteStates)
+                {
+                    args.Sprite.LayerSetState(layer, layerState);
+                }
+                break;
+            case DoorState.Opening:
+                if (animPlayer != null && comp.OpeningAnimation != default)
+                    _animationSystem.Play(uid, animPlayer, (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
+                break;
+            case DoorState.Closing:
+                if (animPlayer != null && comp.ClosingAnimation != default && comp.CurrentlyCrushing.Count == 0)
+                    _animationSystem.Play(uid, animPlayer, (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
+                break;
+            case DoorState.Denying:
+                if (animPlayer != null && comp.DenyingAnimation != default)
+                    _animationSystem.Play(uid, animPlayer, (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
+                break;
+            case DoorState.Welded:
+                break;
+            case DoorState.Emagging:
+                if (animPlayer != null && comp.EmaggingAnimation != default)
+                    _animationSystem.Play(uid, animPlayer, (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
+                break;
+            default:
+                throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
         }
     }
 
index 6152a4ae1431afa60cd12d1a268724a0d66c42e1..2d343da615cc6f4d41fefc2661059fde6722c89e 100644 (file)
@@ -1,4 +1,3 @@
-using System.Threading;
 using Content.Shared.Doors.Systems;
 using Content.Shared.MachineLinking;
 using Robust.Shared.Audio;
@@ -56,8 +55,16 @@ public sealed class AirlockComponent : Component
     [DataField("keepOpenIfClicked")]
     public bool KeepOpenIfClicked = false;
 
+    /// <summary>
+    /// Whether the door bolts are currently deployed.
+    /// </summary>
+    [ViewVariables]
     public bool BoltsDown;
 
+    /// <summary>
+    /// Whether the bolt lights are currently enabled.
+    /// </summary>
+    [ViewVariables]
     public bool BoltLightsEnabled = true;
 
     /// <summary>
@@ -86,10 +93,80 @@ public sealed class AirlockComponent : Component
     public float AutoCloseDelayModifier = 1.0f;
 
     /// <summary>
-    ///     The receiver port for turning off automatic closing.
+    /// The receiver port for turning off automatic closing.
     /// </summary>
     [DataField("autoClosePort", customTypeSerializer: typeof(PrototypeIdSerializer<ReceiverPortPrototype>))]
     public string AutoClosePort = "AutoClose";
+
+    #region Graphics
+
+    /// <summary>
+    /// Whether the door lights should be visible.
+    /// </summary>
+    [DataField("openUnlitVisible")]
+    public bool OpenUnlitVisible = false;
+
+    /// <summary>
+    /// Whether the door should display emergency access lights.
+    /// </summary>
+    [DataField("emergencyAccessLayer")]
+    public bool EmergencyAccessLayer = true;
+
+    /// <summary>
+    /// Whether or not to animate the panel when the door opens or closes.
+    /// </summary>
+    [DataField("animatePanel")]
+    public bool AnimatePanel = true;
+
+    /// <summary>
+    /// The sprite state used to animate the airlock frame when the airlock opens.
+    /// </summary>
+    [DataField("openingSpriteState")]
+    public string OpeningSpriteState = "opening_unlit";
+
+    /// <summary>
+    /// The sprite state used to animate the airlock panel when the airlock opens.
+    /// </summary>
+    [DataField("openingPanelSpriteState")]
+    public string OpeningPanelSpriteState = "panel_opening";
+
+    /// <summary>
+    /// The sprite state used to animate the airlock frame when the airlock closes.
+    /// </summary>
+    [DataField("closingSpriteState")]
+    public string ClosingSpriteState = "closing_unlit";
+
+    /// <summary>
+    /// The sprite state used to animate the airlock panel when the airlock closes.
+    /// </summary>
+    [DataField("closingPanelSpriteState")]
+    public string ClosingPanelSpriteState = "panel_closing";
+
+    /// <summary>
+    /// The sprite state used for the open airlock lights.
+    /// </summary>
+    [DataField("openSpriteState")]
+    public string OpenSpriteState = "open_unlit";
+
+    /// <summary>
+    /// The sprite state used for the closed airlock lights.
+    /// </summary>
+    [DataField("closedSpriteState")]
+    public string ClosedSpriteState = "closed_unlit";
+
+    /// <summary>
+    /// The sprite state used for the 'access denied' lights animation.
+    /// </summary>
+    [DataField("denySpriteState")]
+    public string DenySpriteState = "deny_unlit";
+
+    /// <summary>
+    /// How long the animation played when the airlock denies access is in seconds.
+    /// </summary>
+    [DataField("denyAnimationTime")]
+    public float DenyAnimationTime = 0.3f;
+
+    #endregion Graphics
 }
 
 [Serializable, NetSerializable]
index 69fab8ee9e4911b425bff251c15a333cfe39756c..242d8f243db6a5e9c9c4706ee02f8560d9a91195 100644 (file)
@@ -1,4 +1,6 @@
+using System.Runtime.InteropServices;
 using Content.Shared.Damage;
+using Content.Shared.Doors.Systems;
 using Content.Shared.Tools;
 using JetBrains.Annotations;
 using Robust.Shared.Audio;
@@ -13,16 +15,17 @@ namespace Content.Shared.Doors.Components;
 
 [NetworkedComponent]
 [RegisterComponent]
-public sealed class DoorComponent : Component, ISerializationHooks
+public sealed class DoorComponent : Component
 {
     /// <summary>
     /// The current state of the door -- whether it is open, closed, opening, or closing.
     /// </summary>
     /// <remarks>
-    /// This should never be set directly.
+    /// This should never be set directly, use <see cref="SharedDoorSystem.SetState(EntityUid, DoorState, DoorComponent?)"/> instead.
     /// </remarks>
     [ViewVariables(VVAccess.ReadWrite)]
     [DataField("state")]
+    [Access(typeof(SharedDoorSystem))]
     public DoorState State = DoorState.Closed;
 
     #region Timing
@@ -139,6 +142,97 @@ public sealed class DoorComponent : Component, ISerializationHooks
     public HashSet<EntityUid> CurrentlyCrushing = new();
     #endregion
 
+    #region Graphics
+
+    /// <summary>
+    /// The key used when playing door opening/closing/emagging/deny animations.
+    /// </summary>
+    public const string AnimationKey = "door_animation";
+
+    /// <summary>
+    /// The sprite state used for the door when it's open.
+    /// </summary>
+    [DataField("openSpriteState")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public string OpenSpriteState = "open";
+
+    /// <summary>
+    /// The sprite states used for the door while it's open.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly)]
+    public List<(DoorVisualLayers, string)> OpenSpriteStates = default!;
+
+    /// <summary>
+    /// The sprite state used for the door when it's closed.
+    /// </summary>
+    [DataField("closedSpriteState")]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public string ClosedSpriteState = "closed";
+
+    /// <summary>
+    /// The sprite states used for the door while it's closed.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly)]
+    public List<(DoorVisualLayers, string)> ClosedSpriteStates = default!;
+
+    /// <summary>
+    /// The sprite state used for the door when it's opening.
+    /// </summary>
+    [DataField("openingSpriteState")]
+    public string OpeningSpriteState = "opening";
+
+    /// <summary>
+    /// The sprite state used for the door when it's closing.
+    /// </summary>
+    [DataField("closingSpriteState")]
+    public string ClosingSpriteState = "closing";
+
+    /// <summary>
+    /// The sprite state used for the door when it's being emagged.
+    /// </summary>
+    [DataField("emaggingSpriteState")]
+    public string EmaggingSpriteState = "emagging";
+
+    /// <summary>
+    /// The sprite state used for the door when it's open.
+    /// </summary>
+    [DataField("openingAnimationTime")]
+    public float OpeningAnimationTime = 0.8f;
+
+    /// <summary>
+    /// The sprite state used for the door when it's open.
+    /// </summary>
+    [DataField("closingAnimationTime")]
+    public float ClosingAnimationTime = 0.8f;
+
+    /// <summary>
+    /// The sprite state used for the door when it's open.
+    /// </summary>
+    [DataField("emaggingAnimationTime")]
+    public float EmaggingAnimationTime = 1.5f;
+
+    /// <summary>
+    /// The animation used when the door opens.
+    /// </summary>
+    public object OpeningAnimation = default!;
+
+    /// <summary>
+    /// The animation used when the door closes.
+    /// </summary>
+    public object ClosingAnimation = default!;
+
+    /// <summary>
+    /// The animation used when the door denies access.
+    /// </summary>
+    public object DenyingAnimation = default!;
+
+    /// <summary>
+    /// The animation used when the door is emagged.
+    /// </summary>
+    public object EmaggingAnimation = default!;
+
+    #endregion Graphics
+
     #region Serialization
     /// <summary>
     ///     Time until next state change. Because apparently <see cref="IGameTiming.CurTime"/> might not get saved/restored.
@@ -209,7 +303,7 @@ public sealed class DoorComponent : Component, ISerializationHooks
 }
 
 [Serializable, NetSerializable]
-public enum DoorState
+public enum DoorState : byte
 {
     Closed,
     Closing,
@@ -221,7 +315,7 @@ public enum DoorState
 }
 
 [Serializable, NetSerializable]
-public enum DoorVisuals
+public enum DoorVisuals : byte
 {
     State,
     Powered,
@@ -231,6 +325,14 @@ public enum DoorVisuals
     BaseRSI,
 }
 
+public enum DoorVisualLayers : byte
+{
+    Base,
+    BaseUnlit,
+    BaseBolted,
+    BaseEmergencyAccess,
+}
+
 [Serializable, NetSerializable]
 public sealed class DoorComponentState : ComponentState
 {
index 4a773a73812130cab6be30e30337988e1e7f841a..c105f69120e98343dc2f07e062313957379350e4 100644 (file)
@@ -27,7 +27,7 @@ public abstract class SharedDoorSystem : EntitySystem
     [Dependency] protected readonly TagSystem Tags = default!;
     [Dependency] protected readonly SharedAudioSystem Audio = default!;
     [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
     [Dependency] private readonly OccluderSystem _occluder = default!;
 
     /// <summary>
@@ -49,7 +49,7 @@ public abstract class SharedDoorSystem : EntitySystem
     {
         base.Initialize();
 
-        SubscribeLocalEvent<DoorComponent, ComponentInit>(OnInit);
+        SubscribeLocalEvent<DoorComponent, ComponentInit>(OnComponentInit);
         SubscribeLocalEvent<DoorComponent, ComponentRemove>(OnRemove);
 
         SubscribeLocalEvent<DoorComponent, ComponentGetState>(OnGetState);
@@ -61,7 +61,7 @@ public abstract class SharedDoorSystem : EntitySystem
         SubscribeLocalEvent<DoorComponent, PreventCollideEvent>(PreventCollision);
     }
 
-    private void OnInit(EntityUid uid, DoorComponent door, ComponentInit args)
+    protected virtual void OnComponentInit(EntityUid uid, DoorComponent door, ComponentInit args)
     {
         if (door.NextStateChange != null)
             _activeDoors.Add(door);
@@ -88,7 +88,7 @@ public abstract class SharedDoorSystem : EntitySystem
             || door.State == DoorState.Opening && !door.Partial;
 
         SetCollidable(uid, collidable, door);
-        UpdateAppearance(uid, door);
+        AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
     }
 
     private void OnRemove(EntityUid uid, DoorComponent door, ComponentRemove args)
@@ -123,7 +123,7 @@ public abstract class SharedDoorSystem : EntitySystem
             _activeDoors.Add(door);
 
         RaiseLocalEvent(uid, new DoorStateChangedEvent(door.State), false);
-        UpdateAppearance(uid, door);
+        AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
     }
 
     protected void SetState(EntityUid uid, DoorState state, DoorComponent? door = null)
@@ -167,19 +167,9 @@ public abstract class SharedDoorSystem : EntitySystem
         door.State = state;
         Dirty(door);
         RaiseLocalEvent(uid, new DoorStateChangedEvent(state), false);
-        UpdateAppearance(uid, door);
+        AppearanceSystem.SetData(uid, DoorVisuals.State, door.State);
     }
 
-    protected virtual void UpdateAppearance(EntityUid uid, DoorComponent? door = null)
-    {
-        if (!Resolve(uid, ref door))
-            return;
-
-        if (!TryComp(uid, out AppearanceComponent? appearance))
-            return;
-
-        _appearance.SetData(uid, DoorVisuals.State, door.State);
-    }
     #endregion
 
     #region Interactions
@@ -365,7 +355,7 @@ public abstract class SharedDoorSystem : EntitySystem
         {
             door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo;
             door.State = DoorState.Opening;
-            UpdateAppearance(uid, door);
+            AppearanceSystem.SetData(uid, DoorVisuals.State, DoorState.Opening);
             return false;
         }
 
index acd48ec7893160bfd6d81ec6f2e174918927c629..a478418b2e9f6292a6d9e2daa1e95951f9e16af3 100644 (file)
@@ -12,6 +12,7 @@
     layers:
     - state: closed
       map: ["enum.DoorVisualLayers.Base"]
+  - type: AnimationPlayer
   - type: Fixtures
     fixtures:
     - shape:
@@ -36,9 +37,6 @@
     closeSound:
       path: /Audio/Effects/curtain_openclose.ogg
   - type: Appearance
-    visuals:
-      - type: AirlockVisualizer
-        simpleVisuals: true
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Wood
index 6d7324edf76d5db4bc48b9b1ef2baacb1f56d006..cd0bb007ddc3cc98027d79f5269660c091293722 100644 (file)
     enabled: false
   - type: Sprite
     sprite: Structures/Doors/Airlocks/Glass/glass.rsi
+  - type: AnimationPlayer
   - type: Fixtures
     fixtures:
     - shape:
index 490c44ac3d26cf0d119dee0b3fa97a830a390b14..ada2ac8bb8d68cec172a22578f8e3439d22f4ce6 100644 (file)
@@ -30,6 +30,7 @@
       shader: unshaded
     - state: panel_open
       map: ["enum.WiresVisualLayers.MaintenancePanel"]
+  - type: AnimationPlayer
   - type: Physics
   - type: Fixtures
     fixtures:
@@ -65,8 +66,6 @@
     time: 3
   - type: Airlock
   - type: Appearance
-    visuals:
-    - type: AirlockVisualizer
   - type: WiresVisuals
   - type: ApcPowerReceiver
     powerLoad: 20
index 7eeddc9dd23b31412aab2a284e700fc94152ebd3..c6f39c1f638996209fc79cdc569d3583cfafde75 100644 (file)
@@ -18,8 +18,6 @@
   - type: Sprite
     sprite: Structures/Doors/Airlocks/Standard/external.rsi
   - type: Appearance
-    visuals:
-    - type: AirlockVisualizer
   - type: WiresVisuals
   - type: PaintableAirlock
     group: External
index 5c0259437ca82b2e502a0488303a1b163569df08..06e702011ca8ebc14229df369404a558a1fa9b22 100644 (file)
@@ -24,6 +24,7 @@
       shader: unshaded\r
     - state: panel_open\r
       map: ["enum.WiresVisualLayers.MaintenancePanel"]\r
+  - type: AnimationPlayer\r
   - type: Physics\r
   - type: Fixtures\r
     fixtures:\r
@@ -56,8 +57,6 @@
     time: 10\r
   - type: Airlock\r
   - type: Appearance\r
-    visuals:\r
-    - type: AirlockVisualizer\r
   - type: WiresVisuals\r
   - type: ApcPowerReceiver\r
     powerLoad: 20\r
index 1e51bb1e14d6cf28ce4990d6992a7e979ad995ad..7f11b5e8ae509b0eb0f15f8a61ce5886dab75c12 100644 (file)
         - state: closed_unlit
           shader: unshaded
           map: ["enum.DoorVisualLayers.BaseUnlit"]
+          visible: false
         - state: welded
           map: ["enum.WeldableLayers.BaseWelded"]
         - state: bolted_unlit
           shader: unshaded
           map: ["enum.DoorVisualLayers.BaseBolted"]
+          visible: false
         - state: panel_open
           map: ["enum.WiresVisualLayers.MaintenancePanel"]
+    - type: AnimationPlayer
     - type: Fixtures
       fixtures:
       - shape:
         path: /Audio/Machines/airlock_close.ogg
       denySound:
         path: /Audio/Machines/airlock_deny.ogg
+      openingAnimationTime: 0.6
+      closingAnimationTime: 0.6
     - type: Weldable
       fuel: 3
       time: 3
     - type: Firelock
     - type: Appearance
-      visuals:
-        - type: AirlockVisualizer
-          animationTime: 0.6
-          emergencyAccessLayer: false
     - type: WiresVisuals
     - type: Wires
       BoardName: "Firelock Control"
index 343521c3c0aa7b3ea42774d07557751ef4c75874..09bca7ec0a3ecf1c20427bb0d66ce90b70283052 100644 (file)
@@ -12,6 +12,7 @@
     layers:
     - state: closed
       map: ["enum.DoorVisualLayers.Base"]
+  - type: AnimationPlayer
   - type: Physics
   - type: Fixtures
     fixtures:
     closeTimeTwo: 0.6
     openTimeOne: 0.6
     openTimeTwo: 0.2
+    openingAnimationTime: 1.2
+    closingAnimationTime: 1.2
     openSound:
       path: /Audio/Effects/stonedoor_openclose.ogg
     closeSound:
       path: /Audio/Effects/stonedoor_openclose.ogg
   - type: Appearance
-    visuals:
-      - type: AirlockVisualizer
-        simpleVisuals: true
-        animationTime: 1.2
   - type: Airtight
     fixVacuum: true
   - type: Damageable
index 28edd48109b544205bb44a7213981731814d733e..b43222016fd2685490dc5e429bb4ce10f43731ca 100644 (file)
     closeTimeTwo: 0.4
     openTimeOne: 0.4
     openTimeTwo: 0.4
+    openingAnimationTime: 1.0
+    closingAnimationTime: 1.0
     pryTime: -1
     crushDamage:
       types:
         Blunt: 25 # yowch
   - type: Occluder
   - type: Appearance
-    visuals:
-    - type: AirlockVisualizer
-      simpleVisuals: true
-      animationTime: 1.0
   - type: RadiationBlocker
     resistance: 8
 
index bcb71b2765b8ce00f36c57449daa79d8d310101a..f16719975ed926efcaa68e6090eced39d2c09726 100644 (file)
@@ -14,6 +14,7 @@
     layers:
     - state: closed
       map: ["enum.DoorVisualLayers.Base"]
+  - type: AnimationPlayer
   - type: Physics
   - type: Fixtures
     fixtures:
@@ -38,6 +39,8 @@
     closeTimeTwo: 1.2
     openTimeOne: 1.2
     openTimeTwo: 0.2
+    openingAnimationTime: 1.4
+    closingAnimationTime: 1.4
     crushDamage:
       types:
         Blunt: 5 # getting shutters closed on you probably doesn't hurt that much
     fuel: 3
     time: 3
   - type: Appearance
-    visuals:
-      - type: AirlockVisualizer
-        simpleVisuals: true
-        animationTime: 1.4
   - type: UserInterface
     interfaces:
     - key: enum.WiresUiKey.Key
index f58dd89ef087a1545f7e38b84a1e192920808656..58f79eec9693db918f91a72ba64e21ee21174b30 100644 (file)
@@ -41,6 +41,7 @@
       map: ["enum.DoorVisualLayers.BaseEmergencyAccess"]
     - state: panel_open
       map: ["enum.WiresVisualLayers.MaintenancePanel"]
+  - type: AnimationPlayer
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DoorSignalControl
       - !type:DoActsBehavior
         acts: [ "Destruction" ]
   - type: AccessReader
-  - type: Airlock
-    keepOpenIfClicked: true
-    openPanelVisible: true
-    # needed so that windoors will close regardless of whether there are people in it; it doesn't crush after all
-    safety: false
   - type: ContainerFill
     containers:
       board: [ DoorElectronics ]
       board: !type:Container
   - type: Door
     canCrush: false
+    openingAnimationTime: 0.9
+    closingAnimationTime: 0.9
     openSound:
       path: /Audio/Machines/windoor_open.ogg
     closeSound:
       path: /Audio/Machines/windoor_open.ogg
     denySound:
       path: /Audio/Machines/airlock_deny.ogg
+  - type: Airlock
+    keepOpenIfClicked: true
+    openPanelVisible: true
+    denyAnimationTime: 0.4
+    animatePanel: false
+    openUnlitVisible: true
+    # needed so that windoors will close regardless of whether there are people in it; it doesn't crush after all
+    safety: false
   - type: Electrified
     enabled: false
     usesApcPower: true
     - key: enum.WiresUiKey.Key
       type: WiresBoundUserInterface
   - type: Appearance
-    visuals:
-      - type: AirlockVisualizer
-        animationTime: 0.9
-        denyAnimationTime: 0.4
-        animatedPanel: false
-        openUnlitVisible: true
   - type: WiresVisuals
   - type: Airtight
     fixVacuum: true