From 7523ed4c178359d5d7c55c07d94d39503606240b Mon Sep 17 00:00:00 2001 From: TemporalOroboros Date: Sat, 22 Apr 2023 02:18:16 -0700 Subject: [PATCH] Resolves AirlockVisualizer is Obsolete (#13884) --- Content.Client/Doors/AirlockSystem.cs | 109 +++++++- Content.Client/Doors/AirlockVisualizer.cs | 256 ------------------ Content.Client/Doors/DoorSystem.cs | 125 ++++++++- .../Doors/Components/AirlockComponent.cs | 81 +++++- .../Doors/Components/DoorComponent.cs | 110 +++++++- .../Doors/Systems/SharedDoorSystem.cs | 24 +- .../Structures/Decoration/curtains.yml | 4 +- .../Structures/Doors/Airlocks/airlocks.yml | 1 + .../Doors/Airlocks/base_structureairlocks.yml | 3 +- .../Structures/Doors/Airlocks/external.yml | 2 - .../Structures/Doors/Airlocks/highsec.yml | 3 +- .../Structures/Doors/Firelocks/firelock.yml | 9 +- .../Doors/MaterialDoors/material_doors.yml | 7 +- .../Structures/Doors/Shutter/blast_door.yml | 6 +- .../Structures/Doors/Shutter/shutters.yml | 7 +- .../Doors/Windoors/base_structurewindoors.yml | 22 +- 16 files changed, 444 insertions(+), 325 deletions(-) delete mode 100644 Content.Client/Doors/AirlockVisualizer.cs diff --git a/Content.Client/Doors/AirlockSystem.cs b/Content.Client/Doors/AirlockSystem.cs index 1898edb621..cf4c3dfca0 100644 --- a/Content.Client/Doors/AirlockSystem.cs +++ b/Content.Client/Doors/AirlockSystem.cs @@ -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(OnComponentStartup); + SubscribeLocalEvent(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(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(uid, DoorVisuals.State, out var state, args.Component)) + state = DoorState.Closed; + + if (_appearanceSystem.TryGetData(uid, DoorVisuals.Powered, out var powered, args.Component) && powered) + { + boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights; + emergencyLightsVisible = _appearanceSystem.TryGetData(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(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 index c4214aaf64..0000000000 --- a/Content.Client/Doors/AirlockVisualizer.cs +++ /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; - - /// - /// Whether the maintenance panel is animated or stays static. - /// False for windoors. - /// - [DataField("animatedPanel")] - private bool _animatedPanel = true; - - /// - /// Means the door is simply open / closed / opening / closing. No wires or access. - /// - [DataField("simpleVisuals")] - private bool _simpleVisuals = false; - - /// - /// Whether the BaseUnlit layer should still be visible when the airlock - /// is opened. - /// - [DataField("openUnlitVisible")] - private bool _openUnlitVisible = false; - - /// - /// Whether the door should have an emergency access layer - /// - [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(entity)) - { - _entMan.AddComponent(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(component.Owner); - var animPlayer = _entMan.GetComponent(component.Owner); - if (!component.TryGetData(DoorVisuals.State, out DoorState state)) - { - state = DoorState.Closed; - } - - var door = _entMan.GetComponent(component.Owner); - - if (component.TryGetData(DoorVisuals.BaseRSI, out string baseRsi)) - { - if (!_resourceCache.TryGetResource(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, - } -} diff --git a/Content.Client/Doors/DoorSystem.cs b/Content.Client/Doors/DoorSystem.cs index ac393c519b..c77412ac65 100644 --- a/Content.Client/Doors/DoorSystem.cs +++ b/Content.Client/Doors/DoorSystem.cs @@ -1,28 +1,135 @@ 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(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(uid, DoorVisuals.State, out var state, args.Component)) + state = DoorState.Closed; + + if (AppearanceSystem.TryGetData(uid, DoorVisuals.BaseRSI, out var baseRsi, args.Component)) + { + if (!_resourceCache.TryGetResource(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(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}"); } } diff --git a/Content.Shared/Doors/Components/AirlockComponent.cs b/Content.Shared/Doors/Components/AirlockComponent.cs index 6152a4ae14..2d343da615 100644 --- a/Content.Shared/Doors/Components/AirlockComponent.cs +++ b/Content.Shared/Doors/Components/AirlockComponent.cs @@ -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; + /// + /// Whether the door bolts are currently deployed. + /// + [ViewVariables] public bool BoltsDown; + /// + /// Whether the bolt lights are currently enabled. + /// + [ViewVariables] public bool BoltLightsEnabled = true; /// @@ -86,10 +93,80 @@ public sealed class AirlockComponent : Component public float AutoCloseDelayModifier = 1.0f; /// - /// The receiver port for turning off automatic closing. + /// The receiver port for turning off automatic closing. /// [DataField("autoClosePort", customTypeSerializer: typeof(PrototypeIdSerializer))] public string AutoClosePort = "AutoClose"; + + #region Graphics + + /// + /// Whether the door lights should be visible. + /// + [DataField("openUnlitVisible")] + public bool OpenUnlitVisible = false; + + /// + /// Whether the door should display emergency access lights. + /// + [DataField("emergencyAccessLayer")] + public bool EmergencyAccessLayer = true; + + /// + /// Whether or not to animate the panel when the door opens or closes. + /// + [DataField("animatePanel")] + public bool AnimatePanel = true; + + /// + /// The sprite state used to animate the airlock frame when the airlock opens. + /// + [DataField("openingSpriteState")] + public string OpeningSpriteState = "opening_unlit"; + + /// + /// The sprite state used to animate the airlock panel when the airlock opens. + /// + [DataField("openingPanelSpriteState")] + public string OpeningPanelSpriteState = "panel_opening"; + + /// + /// The sprite state used to animate the airlock frame when the airlock closes. + /// + [DataField("closingSpriteState")] + public string ClosingSpriteState = "closing_unlit"; + + /// + /// The sprite state used to animate the airlock panel when the airlock closes. + /// + [DataField("closingPanelSpriteState")] + public string ClosingPanelSpriteState = "panel_closing"; + + /// + /// The sprite state used for the open airlock lights. + /// + [DataField("openSpriteState")] + public string OpenSpriteState = "open_unlit"; + + /// + /// The sprite state used for the closed airlock lights. + /// + [DataField("closedSpriteState")] + public string ClosedSpriteState = "closed_unlit"; + + /// + /// The sprite state used for the 'access denied' lights animation. + /// + [DataField("denySpriteState")] + public string DenySpriteState = "deny_unlit"; + + /// + /// How long the animation played when the airlock denies access is in seconds. + /// + [DataField("denyAnimationTime")] + public float DenyAnimationTime = 0.3f; + + #endregion Graphics } [Serializable, NetSerializable] diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs index 69fab8ee9e..242d8f243d 100644 --- a/Content.Shared/Doors/Components/DoorComponent.cs +++ b/Content.Shared/Doors/Components/DoorComponent.cs @@ -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 { /// /// The current state of the door -- whether it is open, closed, opening, or closing. /// /// - /// This should never be set directly. + /// This should never be set directly, use instead. /// [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 CurrentlyCrushing = new(); #endregion + #region Graphics + + /// + /// The key used when playing door opening/closing/emagging/deny animations. + /// + public const string AnimationKey = "door_animation"; + + /// + /// The sprite state used for the door when it's open. + /// + [DataField("openSpriteState")] + [ViewVariables(VVAccess.ReadWrite)] + public string OpenSpriteState = "open"; + + /// + /// The sprite states used for the door while it's open. + /// + [ViewVariables(VVAccess.ReadOnly)] + public List<(DoorVisualLayers, string)> OpenSpriteStates = default!; + + /// + /// The sprite state used for the door when it's closed. + /// + [DataField("closedSpriteState")] + [ViewVariables(VVAccess.ReadWrite)] + public string ClosedSpriteState = "closed"; + + /// + /// The sprite states used for the door while it's closed. + /// + [ViewVariables(VVAccess.ReadOnly)] + public List<(DoorVisualLayers, string)> ClosedSpriteStates = default!; + + /// + /// The sprite state used for the door when it's opening. + /// + [DataField("openingSpriteState")] + public string OpeningSpriteState = "opening"; + + /// + /// The sprite state used for the door when it's closing. + /// + [DataField("closingSpriteState")] + public string ClosingSpriteState = "closing"; + + /// + /// The sprite state used for the door when it's being emagged. + /// + [DataField("emaggingSpriteState")] + public string EmaggingSpriteState = "emagging"; + + /// + /// The sprite state used for the door when it's open. + /// + [DataField("openingAnimationTime")] + public float OpeningAnimationTime = 0.8f; + + /// + /// The sprite state used for the door when it's open. + /// + [DataField("closingAnimationTime")] + public float ClosingAnimationTime = 0.8f; + + /// + /// The sprite state used for the door when it's open. + /// + [DataField("emaggingAnimationTime")] + public float EmaggingAnimationTime = 1.5f; + + /// + /// The animation used when the door opens. + /// + public object OpeningAnimation = default!; + + /// + /// The animation used when the door closes. + /// + public object ClosingAnimation = default!; + + /// + /// The animation used when the door denies access. + /// + public object DenyingAnimation = default!; + + /// + /// The animation used when the door is emagged. + /// + public object EmaggingAnimation = default!; + + #endregion Graphics + #region Serialization /// /// Time until next state change. Because apparently 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 { diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 4a773a7381..c105f69120 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -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!; /// @@ -49,7 +49,7 @@ public abstract class SharedDoorSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnGetState); @@ -61,7 +61,7 @@ public abstract class SharedDoorSystem : EntitySystem SubscribeLocalEvent(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; } diff --git a/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml b/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml index acd48ec789..a478418b2e 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml index 6d7324edf7..cd0bb007dd 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml @@ -104,6 +104,7 @@ enabled: false - type: Sprite sprite: Structures/Doors/Airlocks/Glass/glass.rsi + - type: AnimationPlayer - type: Fixtures fixtures: - shape: diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 490c44ac3d..ada2ac8bb8 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml index 7eeddc9dd2..c6f39c1f63 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml @@ -18,8 +18,6 @@ - type: Sprite sprite: Structures/Doors/Airlocks/Standard/external.rsi - type: Appearance - visuals: - - type: AirlockVisualizer - type: WiresVisuals - type: PaintableAirlock group: External diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml index 5c0259437c..06e702011c 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml @@ -24,6 +24,7 @@ shader: unshaded - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: AnimationPlayer - type: Physics - type: Fixtures fixtures: @@ -56,8 +57,6 @@ time: 10 - type: Airlock - type: Appearance - visuals: - - type: AirlockVisualizer - type: WiresVisuals - type: ApcPowerReceiver powerLoad: 20 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 1e51bb1e14..7f11b5e8ae 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -37,13 +37,16 @@ - 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: @@ -72,15 +75,13 @@ 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" diff --git a/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml b/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml index 343521c3c0..09bca7ec0a 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml @@ -12,6 +12,7 @@ layers: - state: closed map: ["enum.DoorVisualLayers.Base"] + - type: AnimationPlayer - type: Physics - type: Fixtures fixtures: @@ -31,15 +32,13 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml index 28edd48109..b43222016f 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml @@ -15,16 +15,14 @@ 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 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml index bcb71b2765..f16719975e 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml @@ -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 @@ -49,10 +52,6 @@ fuel: 3 time: 3 - type: Appearance - visuals: - - type: AirlockVisualizer - simpleVisuals: true - animationTime: 1.4 - type: UserInterface interfaces: - key: enum.WiresUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index f58dd89ef0..58f79eec96 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -41,6 +41,7 @@ map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: AnimationPlayer - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DoorSignalControl @@ -78,11 +79,6 @@ - !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 ] @@ -91,12 +87,22 @@ 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 @@ -108,12 +114,6 @@ - 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 -- 2.51.2