From af2e21c3559f01c88574b2a58398e074c895c80f Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Mon, 2 Aug 2021 04:57:06 -0700 Subject: [PATCH] Refactor IDoorCheck into entity events (#4366) * IDoorCheck refactored to events # Conflicts: # Content.Server/Atmos/TileAtmosphere.cs # Content.Server/Doors/Components/AirlockComponent.cs # Content.Server/Doors/Components/FirelockComponent.cs # Content.Server/Doors/Components/ServerDoorComponent.cs # Content.Server/Doors/IDoorCheck.cs * namespaces * Fix mapinit bug with refreshautoclose * ok i guess these just didnt feel like staging today --- Content.Client/Doors/AirlockVisualizer.cs | 27 --- .../AtmosphereSystem.Monstermos.cs | 1 + .../Doors/Components/AirlockComponent.cs | 216 ++++++------------ .../Components/FirelockComponent.cs | 58 ++--- .../Doors/Components/ServerDoorComponent.cs | 156 +++++++++---- Content.Server/Doors/DoorEvents.cs | 141 ++++++++++++ Content.Server/Doors/IDoorCheck.cs | 76 ------ Content.Server/Doors/Systems/AirlockSystem.cs | 109 +++++++++ .../Doors/{ => Systems}/DoorSystem.cs | 0 .../Doors/Systems/FirelockSystem.cs | 69 ++++++ Content.Shared/Doors/SharedDoorComponent.cs | 3 + .../Structures/Doors/Airlocks/base.yml | 9 +- .../Structures/Doors/Airlocks/external.yml | 9 +- .../Structures/Doors/Firelocks/firelock.yml | 9 +- 14 files changed, 545 insertions(+), 338 deletions(-) rename Content.Server/{Atmos => Doors}/Components/FirelockComponent.cs (59%) create mode 100644 Content.Server/Doors/DoorEvents.cs delete mode 100644 Content.Server/Doors/IDoorCheck.cs create mode 100644 Content.Server/Doors/Systems/AirlockSystem.cs rename Content.Server/Doors/{ => Systems}/DoorSystem.cs (100%) create mode 100644 Content.Server/Doors/Systems/FirelockSystem.cs diff --git a/Content.Client/Doors/AirlockVisualizer.cs b/Content.Client/Doors/AirlockVisualizer.cs index 8f6c0651b1..a428a5bc2e 100644 --- a/Content.Client/Doors/AirlockVisualizer.cs +++ b/Content.Client/Doors/AirlockVisualizer.cs @@ -17,15 +17,6 @@ namespace Content.Client.Doors { private const string AnimationKey = "airlock_animation"; - [DataField("open_sound", required: true)] - private string _openSound = default!; - - [DataField("close_sound", required: true)] - private string _closeSound = default!; - - [DataField("deny_sound", required: true)] - private string _denySound = default!; - [DataField("animation_time")] private float _delay = 0.8f; @@ -51,14 +42,6 @@ namespace Content.Client.Doors CloseAnimation.AnimationTracks.Add(flickMaintenancePanel); flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel; flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f)); - - var sound = new AnimationTrackPlaySound(); - CloseAnimation.AnimationTracks.Add(sound); - - if (_closeSound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_closeSound, 0)); - } } OpenAnimation = new Animation {Length = TimeSpan.FromSeconds(_delay)}; @@ -80,11 +63,6 @@ namespace Content.Client.Doors var sound = new AnimationTrackPlaySound(); OpenAnimation.AnimationTracks.Add(sound); - - if (_openSound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_openSound, 0)); - } } DenyAnimation = new Animation {Length = TimeSpan.FromSeconds(0.3f)}; @@ -96,11 +74,6 @@ namespace Content.Client.Doors var sound = new AnimationTrackPlaySound(); DenyAnimation.AnimationTracks.Add(sound); - - if (_denySound != null) - { - sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(_denySound, 0, () => AudioHelpers.WithVariation(0.05f))); - } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index 30b4df6473..87a4f8b2e1 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Collections.Generic; using Content.Server.Atmos.Components; using Content.Server.Coordinates.Helpers; +using Content.Server.Doors.Components; using Content.Shared.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.IoC; diff --git a/Content.Server/Doors/Components/AirlockComponent.cs b/Content.Server/Doors/Components/AirlockComponent.cs index 2e47a1b6d7..dda64b4cda 100644 --- a/Content.Server/Doors/Components/AirlockComponent.cs +++ b/Content.Server/Doors/Components/AirlockComponent.cs @@ -7,12 +7,14 @@ using Content.Shared.Doors; using Content.Shared.Interaction; using Content.Shared.Notification; using Content.Shared.Notification.Managers; +using Content.Shared.Sound; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Player; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; using static Content.Shared.Wires.SharedWiresComponent; using static Content.Shared.Wires.SharedWiresComponent.WiresAction; @@ -23,30 +25,41 @@ namespace Content.Server.Doors.Components /// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing. /// [RegisterComponent] - [ComponentReference(typeof(IDoorCheck))] - public class AirlockComponent : Component, IWires, IDoorCheck + public class AirlockComponent : Component, IWires { public override string Name => "Airlock"; [ComponentDependency] - private readonly ServerDoorComponent? _doorComponent = null; + public readonly ServerDoorComponent? DoorComponent = null; [ComponentDependency] - private readonly SharedAppearanceComponent? _appearanceComponent = null; + public readonly SharedAppearanceComponent? AppearanceComponent = null; [ComponentDependency] - private readonly ApcPowerReceiverComponent? _receiverComponent = null; + public readonly ApcPowerReceiverComponent? ReceiverComponent = null; [ComponentDependency] - private readonly WiresComponent? _wiresComponent = null; + public readonly WiresComponent? WiresComponent = null; + + /// + /// Sound to play when the bolts on the airlock go up. + /// + [DataField("boltUpSound")] + public SoundSpecifier BoltUpSound = new SoundPathSpecifier("/Audio/Machines/boltsup.ogg"); + + /// + /// Sound to play when the bolts on the airlock go down. + /// + [DataField("boltDownSound")] + public SoundSpecifier BoltDownSound = new SoundPathSpecifier("/Audio/Machines/boltsdown.ogg"); /// /// Duration for which power will be disabled after pulsing either power wire. /// - private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0); + [DataField("powerWiresTimeout")] + public float PowerWiresTimeout = 5.0f; private CancellationTokenSource _powerWiresPulsedTimerCancel = new(); - private bool _powerWiresPulsed; /// @@ -83,7 +96,7 @@ namespace Content.Server.Doors.Components private bool BoltLightsVisible { get => _boltLightsWirePulsed && BoltsDown && IsPowered() - && _doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Closed; + && DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Closed; set { _boltLightsWirePulsed = value; @@ -91,120 +104,53 @@ namespace Content.Server.Doors.Components } } - private static readonly TimeSpan AutoCloseDelayFast = TimeSpan.FromSeconds(1); + [ViewVariables(VVAccess.ReadWrite)] + [DataField("autoClose")] + public bool AutoClose = true; [ViewVariables(VVAccess.ReadWrite)] - private bool _autoClose = true; + [DataField("autoCloseDelayModifier")] + public float AutoCloseDelayModifier = 1.0f; [ViewVariables(VVAccess.ReadWrite)] - private bool _normalCloseSpeed = true; - - [ViewVariables(VVAccess.ReadWrite)] - private bool _safety = true; + public bool Safety = true; protected override void Initialize() { base.Initialize(); - if (_receiverComponent != null && _appearanceComponent != null) + if (ReceiverComponent != null && AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.Powered, _receiverComponent.Powered); + AppearanceComponent.SetData(DoorVisuals.Powered, ReceiverComponent.Powered); } } - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - switch (message) - { - case PowerChangedMessage powerChanged: - PowerDeviceOnOnPowerStateChanged(powerChanged); - break; - } - } - - void IDoorCheck.OnStateChange(SharedDoorComponent.DoorState doorState) - { - // Only show the maintenance panel if the airlock is closed - if (_wiresComponent != null) - { - _wiresComponent.IsPanelVisible = doorState != SharedDoorComponent.DoorState.Open; - } - // If the door is closed, we should look if the bolt was locked while closing - UpdateBoltLightStatus(); - } - - bool IDoorCheck.OpenCheck() => CanChangeState(); - - bool IDoorCheck.CloseCheck() => CanChangeState(); - - bool IDoorCheck.DenyCheck() => CanChangeState(); - - bool IDoorCheck.SafetyCheck() => _safety; - - bool IDoorCheck.AutoCloseCheck() => _autoClose; - - TimeSpan? IDoorCheck.GetCloseSpeed() - { - if (_normalCloseSpeed) - { - return null; - } - return AutoCloseDelayFast; - } - - bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) - { - if (_wiresComponent != null && _wiresComponent.IsPanelOpen && - eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - _wiresComponent.OpenInterface(actor.PlayerSession); - return true; - } - return false; - } - - bool IDoorCheck.CanPryCheck(InteractUsingEventArgs eventArgs) - { - if (IsBolted()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message ")); - return false; - } - if (IsPowered()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message")); - return false; - } - return true; - } - - private bool CanChangeState() + public bool CanChangeState() { return IsPowered() && !IsBolted(); } - private bool IsBolted() + public bool IsBolted() { return _boltsDown; } - private bool IsPowered() + public bool IsPowered() { - return _receiverComponent == null || _receiverComponent.Powered; + return ReceiverComponent == null || ReceiverComponent.Powered; } - private void UpdateBoltLightStatus() + public void UpdateBoltLightStatus() { - if (_appearanceComponent != null) + if (AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible); + AppearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible); } } - private void UpdateWiresStatus() + public void UpdateWiresStatus() { - if (_doorComponent == null) + if (DoorComponent == null) { return; } @@ -214,9 +160,9 @@ namespace Content.Server.Doors.Components { powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR"); } - else if (_wiresComponent != null && - _wiresComponent.IsWireCut(Wires.MainPower) && - _wiresComponent.IsWireCut(Wires.BackupPower)) + else if (WiresComponent != null && + WiresComponent.IsWireCut(Wires.MainPower) && + WiresComponent.IsWireCut(Wires.BackupPower)) { powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR"); } @@ -226,63 +172,59 @@ namespace Content.Server.Doors.Components var boltLightsStatus = new StatusLightData(Color.Lime, _boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL"); + var ev = new DoorGetCloseTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + var timingStatus = - new StatusLightData(Color.Orange, !_autoClose ? StatusLightState.Off : - !_normalCloseSpeed ? StatusLightState.BlinkingSlow : + new StatusLightData(Color.Orange, !AutoClose ? StatusLightState.Off : + !MathHelper.CloseTo(ev.CloseTimeModifier, 1.0f) ? StatusLightState.BlinkingSlow : StatusLightState.On, "TIME"); var safetyStatus = - new StatusLightData(Color.Red, _safety ? StatusLightState.On : StatusLightState.Off, "SAFE"); + new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE"); - if (_wiresComponent == null) + if (WiresComponent == null) { return; } - _wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); - _wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); - _wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); - _wiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); - _wiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); - _wiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); - /* - _wires.SetStatus(6, powerLight); - _wires.SetStatus(7, powerLight); - _wires.SetStatus(8, powerLight); - _wires.SetStatus(9, powerLight); - _wires.SetStatus(10, powerLight); - _wires.SetStatus(11, powerLight);*/ + WiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); + WiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus); + WiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus); + WiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); + WiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus); + WiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus); } private void UpdatePowerCutStatus() { - if (_receiverComponent == null) + if (ReceiverComponent == null) { return; } if (PowerWiresPulsed) { - _receiverComponent.PowerDisabled = true; + ReceiverComponent.PowerDisabled = true; return; } - if (_wiresComponent == null) + if (WiresComponent == null) { return; } - _receiverComponent.PowerDisabled = - _wiresComponent.IsWireCut(Wires.MainPower) || - _wiresComponent.IsWireCut(Wires.BackupPower); + ReceiverComponent.PowerDisabled = + WiresComponent.IsWireCut(Wires.MainPower) || + WiresComponent.IsWireCut(Wires.BackupPower); } private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e) { - if (_appearanceComponent != null) + if (AppearanceComponent != null) { - _appearanceComponent.SetData(DoorVisuals.Powered, e.Powered); + AppearanceComponent.SetData(DoorVisuals.Powered, e.Powered); } // BoltLights also got out @@ -341,19 +283,13 @@ namespace Content.Server.Doors.Components builder.CreateWire(Wires.BoltLight); builder.CreateWire(Wires.Timing); builder.CreateWire(Wires.Safety); - /* - builder.CreateWire(6); - builder.CreateWire(7); - builder.CreateWire(8); - builder.CreateWire(9); - builder.CreateWire(10); - builder.CreateWire(11);*/ + UpdateWiresStatus(); } public void WiresUpdate(WiresUpdateEventArgs args) { - if(_doorComponent == null) + if(DoorComponent == null) { return; } @@ -367,7 +303,7 @@ namespace Content.Server.Doors.Components PowerWiresPulsed = true; _powerWiresPulsedTimerCancel.Cancel(); _powerWiresPulsedTimerCancel = new CancellationTokenSource(); - Owner.SpawnTimer(PowerWiresTimeout, + Owner.SpawnTimer(TimeSpan.FromSeconds(PowerWiresTimeout), () => PowerWiresPulsed = false, _powerWiresPulsedTimerCancel.Token); break; @@ -390,11 +326,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = !_boltLightsWirePulsed; break; case Wires.Timing: - _normalCloseSpeed = !_normalCloseSpeed; - _doorComponent.RefreshAutoClose(); + AutoCloseDelayModifier = 0.5f; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = !_safety; + Safety = !Safety; break; } } @@ -413,11 +349,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = true; break; case Wires.Timing: - _autoClose = true; - _doorComponent.RefreshAutoClose(); + AutoClose = true; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = true; + Safety = true; break; } } @@ -433,11 +369,11 @@ namespace Content.Server.Doors.Components BoltLightsVisible = false; break; case Wires.Timing: - _autoClose = false; - _doorComponent.RefreshAutoClose(); + AutoClose = false; + DoorComponent.RefreshAutoClose(); break; case Wires.Safety: - _safety = false; + Safety = false; break; } } @@ -455,7 +391,7 @@ namespace Content.Server.Doors.Components BoltsDown = newBolts; - SoundSystem.Play(Filter.Broadcast(), newBolts ? "/Audio/Machines/boltsdown.ogg" : "/Audio/Machines/boltsup.ogg", Owner); + SoundSystem.Play(Filter.Broadcast(), newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Owner); } } } diff --git a/Content.Server/Atmos/Components/FirelockComponent.cs b/Content.Server/Doors/Components/FirelockComponent.cs similarity index 59% rename from Content.Server/Atmos/Components/FirelockComponent.cs rename to Content.Server/Doors/Components/FirelockComponent.cs index b1849cd757..ee7154bb28 100644 --- a/Content.Server/Atmos/Components/FirelockComponent.cs +++ b/Content.Server/Doors/Components/FirelockComponent.cs @@ -1,3 +1,4 @@ +using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Doors; using Content.Server.Doors.Components; @@ -6,26 +7,34 @@ using Content.Shared.Interaction; using Content.Shared.Notification.Managers; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Robust.Shared.Serialization.Manager.Attributes; -namespace Content.Server.Atmos.Components +namespace Content.Server.Doors.Components { /// - /// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, and not being openable on open-hand click. + /// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying, + /// and not being openable on open-hand click. /// [RegisterComponent] - [ComponentReference(typeof(IDoorCheck))] - public class FirelockComponent : Component, IDoorCheck + public class FirelockComponent : Component { public override string Name => "Firelock"; [ComponentDependency] - private readonly ServerDoorComponent? _doorComponent = null; + public readonly ServerDoorComponent? DoorComponent = null; + + /// + /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure. + /// + /// + [DataField("lockedPryTimeModifier")] + public float LockedPryTimeModifier = 1.5f; public bool EmergencyPressureStop() { - if (_doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Open && _doorComponent.CanCloseGeneric()) + if (DoorComponent != null && DoorComponent.State == SharedDoorComponent.DoorState.Open && DoorComponent.CanCloseGeneric()) { - _doorComponent.Close(); + DoorComponent.Close(); if (Owner.TryGetComponent(out AirtightComponent? airtight)) { EntitySystem.Get().SetAirblocked(airtight, true); @@ -35,41 +44,6 @@ namespace Content.Server.Atmos.Components return false; } - bool IDoorCheck.OpenCheck() - { - return !IsHoldingFire() && !IsHoldingPressure(); - } - - bool IDoorCheck.DenyCheck() => false; - - float? IDoorCheck.GetPryTime() - { - if (IsHoldingFire() || IsHoldingPressure()) - { - return 1.5f; - } - return null; - } - - bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs) => true; - - void IDoorCheck.OnStartPry(InteractUsingEventArgs eventArgs) - { - if (_doorComponent == null || _doorComponent.State != SharedDoorComponent.DoorState.Closed) - { - return; - } - - if (IsHoldingPressure()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-pressure-message")); - } - else if (IsHoldingFire()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("firelock-component-is-holding-fire-message")); - } - } - public bool IsHoldingPressure(float threshold = 20) { var atmosphereSystem = EntitySystem.Get(); diff --git a/Content.Server/Doors/Components/ServerDoorComponent.cs b/Content.Server/Doors/Components/ServerDoorComponent.cs index 8449074d65..1feb49a3b6 100644 --- a/Content.Server/Doors/Components/ServerDoorComponent.cs +++ b/Content.Server/Doors/Components/ServerDoorComponent.cs @@ -14,6 +14,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Doors; using Content.Shared.Interaction; +using Content.Shared.Sound; using Content.Shared.Tool; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -37,9 +38,6 @@ namespace Content.Server.Doors.Components [ComponentReference(typeof(SharedDoorComponent))] public class ServerDoorComponent : SharedDoorComponent, IActivate, IInteractUsing, IMapInit { - [ComponentDependency] - private readonly IDoorCheck? _doorCheck = null; - [ViewVariables] [DataField("board")] private string? _boardPrototype; @@ -63,11 +61,8 @@ namespace Content.Server.Doors.Components _ => throw new ArgumentOutOfRangeException(), }; - if (_doorCheck != null) - { - _doorCheck.OnStateChange(State); - RefreshAutoClose(); - } + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new DoorStateChangedEvent(State), false); + _autoCloseCancelTokenSource?.Cancel(); Dirty(); } @@ -105,7 +100,7 @@ namespace Content.Server.Doors.Components /// Handled in Startup(). /// [ViewVariables(VVAccess.ReadWrite)] [DataField("startOpen")] - private bool _startOpen; + private bool _startOpen = false; /// /// Whether the airlock is welded shut. Can be set by the prototype, although this will fail if the door isn't weldable. @@ -139,6 +134,41 @@ namespace Content.Server.Doors.Components [DataField("weldable")] private bool _weldable = true; + /// + /// Sound to play when the door opens. + /// + [DataField("openSound")] + public SoundSpecifier? OpenSound; + + /// + /// Sound to play when the door closes. + /// + [DataField("closeSound")] + public SoundSpecifier? CloseSound; + + /// + /// Sound to play if the door is denied. + /// + [DataField("denySound")] + public SoundSpecifier? DenySound; + + /// + /// Default time that the door should take to pry open. + /// + [DataField("pryTime")] + public float PryTime = 0.5f; + + /// + /// Minimum interval allowed between deny sounds in milliseconds. + /// + [DataField("denySoundMinimumInterval")] + public float DenySoundMinimumInterval = 250.0f; + + /// + /// Used to stop people from spamming the deny sound. + /// + private TimeSpan LastDenySoundTime = TimeSpan.Zero; + /// /// Whether the door can currently be welded. /// @@ -149,6 +179,7 @@ namespace Content.Server.Doors.Components /// private bool _beingWelded; + //[ViewVariables(VVAccess.ReadWrite)] //[DataField("canCrush")] //private bool _canCrush = true; // TODO implement door crushing @@ -187,7 +218,7 @@ namespace Content.Server.Doors.Components Logger.Warning("{0} prototype loaded with incompatible flags: 'welded' and 'startOpen' are both true.", Owner.Name); return; } - QuickOpen(); + QuickOpen(false); } CreateDoorElectronicsBoard(); @@ -195,10 +226,10 @@ namespace Content.Server.Doors.Components void IActivate.Activate(ActivateEventArgs eventArgs) { - if (_doorCheck != null && _doorCheck.BlockActivate(eventArgs)) - { + DoorClickShouldActivateEvent ev = new DoorClickShouldActivateEvent(eventArgs); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Handled) return; - } if (State == DoorState.Open) { @@ -279,12 +310,10 @@ namespace Content.Server.Doors.Components { return false; } - if(_doorCheck != null) - { - return _doorCheck.OpenCheck(); - } - return true; + var ev = new BeforeDoorOpenedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + return !ev.Cancelled; } /// @@ -301,12 +330,19 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); + if (OpenSound != null) + { + SoundSystem.Play(Filter.Pvs(Owner), OpenSound.GetSound(), + AudioParams.Default.WithVolume(-5)); + } + Owner.SpawnTimer(OpenTimeOne, async () => { OnPartialOpen(); await Timer.Delay(OpenTimeTwo, _stateChangeCancelTokenSource.Token); State = DoorState.Open; + RefreshAutoClose(); }, _stateChangeCancelTokenSource.Token); } @@ -320,7 +356,7 @@ namespace Content.Server.Doors.Components Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new AccessReaderChangeMessage(Owner, false)); } - private void QuickOpen() + private void QuickOpen(bool refresh) { if (Occludes && Owner.TryGetComponent(out OccluderComponent? occluder)) { @@ -328,6 +364,8 @@ namespace Content.Server.Doors.Components } OnPartialOpen(); State = DoorState.Open; + if(refresh) + RefreshAutoClose(); } #endregion @@ -366,17 +404,19 @@ namespace Content.Server.Doors.Components /// Boolean describing whether this door can close. public bool CanCloseGeneric() { - if (_doorCheck != null && !_doorCheck.CloseCheck()) - { + var ev = new BeforeDoorClosedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Cancelled) return false; - } return !IsSafetyColliding(); } private bool SafetyCheck() { - return (_doorCheck != null && _doorCheck.SafetyCheck()) || _inhibitCrush; + var ev = new DoorSafetyEnabledEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + return ev.Safety || _inhibitCrush; } /// @@ -412,6 +452,13 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); + + if (CloseSound != null) + { + SoundSystem.Play(Filter.Pvs(Owner), CloseSound.GetSound(), + AudioParams.Default.WithVolume(-10)); + } + Owner.SpawnTimer(CloseTimeOne, async () => { // if somebody walked into the door as it was closing, and we don't crush things @@ -504,10 +551,10 @@ namespace Content.Server.Doors.Components public void Deny() { - if (_doorCheck != null && !_doorCheck.DenyCheck()) - { + var ev = new BeforeDoorDeniedEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + if (ev.Cancelled) return; - } if (State == DoorState.Open || IsWeldedShut) return; @@ -515,6 +562,25 @@ namespace Content.Server.Doors.Components _stateChangeCancelTokenSource?.Cancel(); _stateChangeCancelTokenSource = new(); SetAppearance(DoorVisualState.Deny); + + if (DenySound != null) + { + if (LastDenySoundTime == TimeSpan.Zero) + { + LastDenySoundTime = _gameTiming.CurTime; + } + else + { + var difference = _gameTiming.CurTime - LastDenySoundTime; + if (difference < TimeSpan.FromMilliseconds(DenySoundMinimumInterval)) + return; + } + + LastDenySoundTime = _gameTiming.CurTime; + SoundSystem.Play(Filter.Pvs(Owner), DenySound.GetSound(), + AudioParams.Default.WithVolume(-3)); + } + Owner.SpawnTimer(DenyTime, () => { SetAppearance(DoorVisualState.Closed); @@ -522,19 +588,24 @@ namespace Content.Server.Doors.Components } /// - /// Stops the current auto-close timer if there is one. Starts a new one if this is appropriate (i.e. entity has an IDoorCheck component that allows auto-closing). + /// Starts a new auto close timer if this is appropriate + /// (i.e. event raised is not cancelled). /// public void RefreshAutoClose() { - _autoCloseCancelTokenSource?.Cancel(); - - if (State != DoorState.Open || _doorCheck == null || !_doorCheck.AutoCloseCheck()) - { + if (State != DoorState.Open) return; - } + + var autoev = new BeforeDoorAutoCloseEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, autoev, false); + if (autoev.Cancelled) + return; + _autoCloseCancelTokenSource = new(); - var realCloseTime = _doorCheck.GetCloseSpeed() ?? AutoCloseDelay; + var ev = new DoorGetCloseTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); + var realCloseTime = AutoCloseDelay * ev.CloseTimeModifier; Owner.SpawnRepeatingTimer(realCloseTime, async () => { @@ -556,21 +627,18 @@ namespace Content.Server.Doors.Components // for prying doors if (tool.HasQuality(ToolQuality.Prying) && !IsWeldedShut) { - var successfulPry = false; + var ev = new DoorGetPryTimeModifierEvent(); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, ev, false); - if (_doorCheck != null) - { - _doorCheck.OnStartPry(eventArgs); - successfulPry = await tool.UseTool(eventArgs.User, Owner, - _doorCheck.GetPryTime() ?? 0.5f, ToolQuality.Prying, () => _doorCheck.CanPryCheck(eventArgs)); - } - else - { - successfulPry = await tool.UseTool(eventArgs.User, Owner, 0.5f, ToolQuality.Prying); - } + var canEv = new BeforeDoorPryEvent(eventArgs); + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, canEv, false); + + var successfulPry = await tool.UseTool(eventArgs.User, Owner, + ev.PryTimeModifier * PryTime, ToolQuality.Prying, () => !canEv.Cancelled); if (successfulPry && !IsWeldedShut) { + Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new OnDoorPryEvent(eventArgs), false); if (State == DoorState.Closed) { Open(); diff --git a/Content.Server/Doors/DoorEvents.cs b/Content.Server/Doors/DoorEvents.cs new file mode 100644 index 0000000000..73a0986664 --- /dev/null +++ b/Content.Server/Doors/DoorEvents.cs @@ -0,0 +1,141 @@ +#nullable enable +using System; +using Content.Shared.Doors; +using Content.Shared.Interaction; +using Robust.Shared.GameObjects; + +namespace Content.Server.Doors +{ + /// + /// Raised when the door's State variable is changed to a new variable that it was not equal to before. + /// + public class DoorStateChangedEvent : EntityEventArgs + { + public SharedDoorComponent.DoorState State; + + public DoorStateChangedEvent(SharedDoorComponent.DoorState state) + { + State = state; + } + } + + /// + /// Raised when the door is determining whether it is able to open. + /// Cancel to stop the door from being opened. + /// + public class BeforeDoorOpenedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when the door is successfully opened. + /// + public class OnDoorOpenedEvent : HandledEntityEventArgs + { + } + + /// + /// Raised when the door is determining whether it is able to close. + /// Cancel to stop the door from being closed. + /// + public class BeforeDoorClosedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when the door is successfully closed. + /// + public class OnDoorClosedEvent : HandledEntityEventArgs + { + } + + /// + /// Called when the door is determining whether it is able to deny. + /// Cancel to stop the door from being able to deny. + /// + public class BeforeDoorDeniedEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised when access to the door is denied. + /// + public class OnDoorDeniedEvent : HandledEntityEventArgs + { + } + + /// + /// Raised to determine whether the door's safety is on. + /// Modify Safety to set the door's safety. + /// + public class DoorSafetyEnabledEvent : HandledEntityEventArgs + { + public bool Safety = false; + } + + /// + /// Raised to determine whether the door should automatically close. + /// Cancel to stop it from automatically closing. + /// + public class BeforeDoorAutoCloseEvent : CancellableEntityEventArgs + { + } + + /// + /// Raised to determine how long the door's pry time should be modified by. + /// Multiply PryTimeModifier by the desired amount. + /// + public class DoorGetPryTimeModifierEvent : EntityEventArgs + { + public float PryTimeModifier = 1.0f; + } + + /// + /// Raised to determine how long the door's close time should be modified by. + /// Multiply CloseTimeModifier by the desired amount. + /// + public class DoorGetCloseTimeModifierEvent : EntityEventArgs + { + public float CloseTimeModifier = 1.0f; + } + + /// + /// Raised to determine whether clicking the door should open/close it. + /// + public class DoorClickShouldActivateEvent : HandledEntityEventArgs + { + public ActivateEventArgs Args; + + public DoorClickShouldActivateEvent(ActivateEventArgs args) + { + Args = args; + } + } + + /// + /// Raised when an attempt to pry open the door is made. + /// Cancel to stop the door from being pried open. + /// + public class BeforeDoorPryEvent : CancellableEntityEventArgs + { + public InteractUsingEventArgs Args; + + public BeforeDoorPryEvent(InteractUsingEventArgs args) + { + Args = args; + } + } + + /// + /// Raised when a door is successfully pried open. + /// + public class OnDoorPryEvent : EntityEventArgs + { + public InteractUsingEventArgs Args; + + public OnDoorPryEvent(InteractUsingEventArgs args) + { + Args = args; + } + } +} diff --git a/Content.Server/Doors/IDoorCheck.cs b/Content.Server/Doors/IDoorCheck.cs deleted file mode 100644 index 3c5989a5d8..0000000000 --- a/Content.Server/Doors/IDoorCheck.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using Content.Shared.Doors; -using Content.Shared.Interaction; - -namespace Content.Server.Doors -{ - public interface IDoorCheck - { - /// - /// Called when the door's State variable is changed to a new variable that it was not equal to before. - /// - void OnStateChange(SharedDoorComponent.DoorState doorState) { } - - /// - /// Called when the door is determining whether it is able to open. - /// - /// True if the door should open, false if it should not. - bool OpenCheck() => true; - - /// - /// Called when the door is determining whether it is able to close. - /// - /// True if the door should close, false if it should not. - bool CloseCheck() => true; - - /// - /// Called when the door is determining whether it is able to deny. - /// - /// True if the door should deny, false if it should not. - bool DenyCheck() => true; - - /// - /// Whether the door's safety is on. - /// - /// True if safety is on, false if it is not. - bool SafetyCheck() => false; - - /// - /// Whether the door should close automatically. - /// - /// True if the door should close automatically, false if it should not. - bool AutoCloseCheck() => false; - - /// - /// Gets an override for the amount of time to pry open the door, or null if there is no override. - /// - /// Float if there is an override, null otherwise. - float? GetPryTime() => null; - - /// - /// Gets an override for the amount of time before the door automatically closes, or null if there is no override. - /// - /// TimeSpan if there is an override, null otherwise. - TimeSpan? GetCloseSpeed() => null; - - /// - /// A check to determine whether or not a click on the door should interact with it with the intent to open/close. - /// - /// True if the door's IActivate should not run, false otherwise. - bool BlockActivate(ActivateEventArgs eventArgs) => false; - - /// - /// Called when somebody begins to pry open the door. - /// - /// The eventArgs of the InteractUsing method that called this function. - void OnStartPry(InteractUsingEventArgs eventArgs) { } - - /// - /// Check representing whether or not the door can be pried open. - /// - /// The eventArgs of the InteractUsing method that called this function. - /// True if the door can be pried open, false if it cannot. - bool CanPryCheck(InteractUsingEventArgs eventArgs) => true; - - } -} diff --git a/Content.Server/Doors/Systems/AirlockSystem.cs b/Content.Server/Doors/Systems/AirlockSystem.cs new file mode 100644 index 0000000000..73d6508272 --- /dev/null +++ b/Content.Server/Doors/Systems/AirlockSystem.cs @@ -0,0 +1,109 @@ +using Content.Server.Doors.Components; +using Content.Server.Power.Components; +using Content.Shared.Doors; +using Content.Shared.Notification.Managers; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Doors.Systems +{ + public class AirlockSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnStateChanged); + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnBeforeDoorClosed); + SubscribeLocalEvent(OnBeforeDoorDenied); + SubscribeLocalEvent(OnDoorSafetyCheck); + SubscribeLocalEvent(OnDoorAutoCloseCheck); + SubscribeLocalEvent(OnDoorCloseTimeModifier); + SubscribeLocalEvent(OnDoorClickShouldActivate); + SubscribeLocalEvent(OnDoorPry); + } + + private void OnPowerChanged(EntityUid uid, AirlockComponent component, PowerChangedEvent args) + { + if (component.AppearanceComponent != null) + { + component.AppearanceComponent.SetData(DoorVisuals.Powered, args.Powered); + } + + // BoltLights also got out + component.UpdateBoltLightStatus(); + } + + private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args) + { + // Only show the maintenance panel if the airlock is closed + if (component.WiresComponent != null) + { + component.WiresComponent.IsPanelVisible = args.State != SharedDoorComponent.DoorState.Open; + } + // If the door is closed, we should look if the bolt was locked while closing + component.UpdateBoltLightStatus(); + } + + private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnBeforeDoorClosed(EntityUid uid, AirlockComponent component, BeforeDoorClosedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args) + { + if (!component.CanChangeState()) + args.Cancel(); + } + + private void OnDoorSafetyCheck(EntityUid uid, AirlockComponent component, DoorSafetyEnabledEvent args) + { + args.Safety = component.Safety; + } + + private void OnDoorAutoCloseCheck(EntityUid uid, AirlockComponent component, BeforeDoorAutoCloseEvent args) + { + if (!component.AutoClose) + args.Cancel(); + } + + private void OnDoorCloseTimeModifier(EntityUid uid, AirlockComponent component, DoorGetCloseTimeModifierEvent args) + { + args.CloseTimeModifier *= component.AutoCloseDelayModifier; + } + + private void OnDoorClickShouldActivate(EntityUid uid, AirlockComponent component, DoorClickShouldActivateEvent args) + { + if (component.WiresComponent != null && component.WiresComponent.IsPanelOpen && + args.Args.User.TryGetComponent(out ActorComponent? actor)) + { + component.WiresComponent.OpenInterface(actor.PlayerSession); + args.Handled = true; + } + } + + private void OnDoorPry(EntityUid uid, AirlockComponent component, BeforeDoorPryEvent args) + { + if (component.IsBolted()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-bolted-message")); + args.Cancel(); + } + if (component.IsPowered()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("airlock-component-cannot-pry-is-powered-message")); + args.Cancel(); + } + } + } +} diff --git a/Content.Server/Doors/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs similarity index 100% rename from Content.Server/Doors/DoorSystem.cs rename to Content.Server/Doors/Systems/DoorSystem.cs diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs new file mode 100644 index 0000000000..49ab584471 --- /dev/null +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -0,0 +1,69 @@ +using Content.Server.Doors.Components; +using Content.Shared.Doors; +using Content.Shared.Notification.Managers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Doors.Systems +{ + public class FirelockSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBeforeDoorOpened); + SubscribeLocalEvent(OnBeforeDoorDenied); + SubscribeLocalEvent(OnDoorGetPryTimeModifier); + SubscribeLocalEvent(OnDoorClickShouldActivate); + SubscribeLocalEvent(OnBeforeDoorPry); + SubscribeLocalEvent(OnBeforeDoorAutoclose); + } + + private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) + { + if (component.IsHoldingFire() || component.IsHoldingPressure()) + args.Cancel(); + } + + private void OnBeforeDoorDenied(EntityUid uid, FirelockComponent component, BeforeDoorDeniedEvent args) + { + args.Cancel(); + } + + private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args) + { + if (component.IsHoldingFire() || component.IsHoldingPressure()) + args.PryTimeModifier *= component.LockedPryTimeModifier; + } + + private void OnDoorClickShouldActivate(EntityUid uid, FirelockComponent component, DoorClickShouldActivateEvent args) + { + // We're a firelock, you can't click to open it + args.Handled = true; + } + + private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args) + { + if (component.DoorComponent == null || component.DoorComponent.State != SharedDoorComponent.DoorState.Closed) + { + return; + } + + if (component.IsHoldingPressure()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-pressure-message")); + } + else if (component.IsHoldingFire()) + { + component.Owner.PopupMessage(args.Args.User, Loc.GetString("firelock-component-is-holding-fire-message")); + } + } + + private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args) + { + // Firelocks can't autoclose, they must be manually closed + args.Cancel(); + } + } +} diff --git a/Content.Shared/Doors/SharedDoorComponent.cs b/Content.Shared/Doors/SharedDoorComponent.cs index db6902c5c6..e85da53ff9 100644 --- a/Content.Shared/Doors/SharedDoorComponent.cs +++ b/Content.Shared/Doors/SharedDoorComponent.cs @@ -22,6 +22,9 @@ namespace Content.Shared.Doors [ComponentDependency] protected readonly IPhysBody? PhysicsComponent = null; + [Dependency] + protected readonly IGameTiming _gameTiming = default!; + [ViewVariables] private DoorState _state = DoorState.Closed; /// diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml index 7b88ff7002..c570660647 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base.yml @@ -38,13 +38,16 @@ - SmallImpassable - type: Door board: DoorElectronics + openSound: + path: /Audio/Machines/airlock_open.ogg + closeSound: + path: /Audio/Machines/airlock_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Airlock - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_open.ogg - close_sound: /Audio/Machines/airlock_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg - type: WiresVisualizer - type: ApcPowerReceiver - type: Wires diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml index b33fa434c7..931987b355 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml @@ -6,12 +6,15 @@ components: - type: Door bumpOpen: false + openSound: + path: /Audio/Machines/airlock_ext_open.ogg + closeSound: + path: /Audio/Machines/airlock_ext_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Sprite sprite: Structures/Doors/Airlocks/Standard/external.rsi - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_ext_open.ogg - close_sound: /Audio/Machines/airlock_ext_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg - type: WiresVisualizer diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 40fad456b0..74cc60c768 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -54,13 +54,16 @@ startOpen: true bumpOpen: false inhibitCrush: false + openSound: + path: /Audio/Machines/airlock_open.ogg + closeSound: + path: /Audio/Machines/airlock_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg - type: Firelock - type: Appearance visuals: - type: AirlockVisualizer - open_sound: /Audio/Machines/airlock_open.ogg - close_sound: /Audio/Machines/airlock_close.ogg - deny_sound: /Audio/Machines/airlock_deny.ogg animation_time: 0.6 - type: WiresVisualizer - type: Wires