+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
+ );
+ }
+ }
+}
+++ /dev/null
-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,
- }
-}
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}");
}
}
-using System.Threading;
using Content.Shared.Doors.Systems;
using Content.Shared.MachineLinking;
using Robust.Shared.Audio;
[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>
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]
+using System.Runtime.InteropServices;
using Content.Shared.Damage;
+using Content.Shared.Doors.Systems;
using Content.Shared.Tools;
using JetBrains.Annotations;
using Robust.Shared.Audio;
[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
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.
}
[Serializable, NetSerializable]
-public enum DoorState
+public enum DoorState : byte
{
Closed,
Closing,
}
[Serializable, NetSerializable]
-public enum DoorVisuals
+public enum DoorVisuals : byte
{
State,
Powered,
BaseRSI,
}
+public enum DoorVisualLayers : byte
+{
+ Base,
+ BaseUnlit,
+ BaseBolted,
+ BaseEmergencyAccess,
+}
+
[Serializable, NetSerializable]
public sealed class DoorComponentState : ComponentState
{
[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>
{
base.Initialize();
- SubscribeLocalEvent<DoorComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<DoorComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DoorComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<DoorComponent, ComponentGetState>(OnGetState);
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);
|| 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)
_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)
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
{
door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo;
door.State = DoorState.Opening;
- UpdateAppearance(uid, door);
+ AppearanceSystem.SetData(uid, DoorVisuals.State, DoorState.Opening);
return false;
}
layers:
- state: closed
map: ["enum.DoorVisualLayers.Base"]
+ - type: AnimationPlayer
- type: Fixtures
fixtures:
- shape:
closeSound:
path: /Audio/Effects/curtain_openclose.ogg
- type: Appearance
- visuals:
- - type: AirlockVisualizer
- simpleVisuals: true
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Wood
enabled: false
- type: Sprite
sprite: Structures/Doors/Airlocks/Glass/glass.rsi
+ - type: AnimationPlayer
- type: Fixtures
fixtures:
- shape:
shader: unshaded
- state: panel_open
map: ["enum.WiresVisualLayers.MaintenancePanel"]
+ - type: AnimationPlayer
- type: Physics
- type: Fixtures
fixtures:
time: 3
- type: Airlock
- type: Appearance
- visuals:
- - type: AirlockVisualizer
- type: WiresVisuals
- type: ApcPowerReceiver
powerLoad: 20
- type: Sprite
sprite: Structures/Doors/Airlocks/Standard/external.rsi
- type: Appearance
- visuals:
- - type: AirlockVisualizer
- type: WiresVisuals
- type: PaintableAirlock
group: External
shader: unshaded\r
- state: panel_open\r
map: ["enum.WiresVisualLayers.MaintenancePanel"]\r
+ - type: AnimationPlayer\r
- type: Physics\r
- type: Fixtures\r
fixtures:\r
time: 10\r
- type: Airlock\r
- type: Appearance\r
- visuals:\r
- - type: AirlockVisualizer\r
- type: WiresVisuals\r
- type: ApcPowerReceiver\r
powerLoad: 20\r
- 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"
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
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
layers:
- state: closed
map: ["enum.DoorVisualLayers.Base"]
+ - type: AnimationPlayer
- type: Physics
- type: Fixtures
fixtures:
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
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