using Content.Shared.DeviceLinking.Events; using Content.Shared.Doors.Components; using Content.Shared.Interaction; using Robust.Shared.Audio.Systems; using Content.Shared.Popups; using Content.Shared.Power; using Content.Shared.Prying.Components; using Content.Shared.Wires; using Robust.Shared.Timing; namespace Content.Shared.Doors.Systems; public abstract partial class SharedAirlockSystem : EntitySystem { [Dependency] private IGameTiming _timing = default!; [Dependency] protected SharedAppearanceSystem Appearance = default!; [Dependency] protected SharedAudioSystem Audio = default!; [Dependency] protected SharedDoorSystem DoorSystem = default!; [Dependency] protected SharedPopupSystem Popup = default!; [Dependency] private SharedWiresSystem _wiresSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnBeforeDoorClosed); SubscribeLocalEvent(OnStateChanged); SubscribeLocalEvent(OnBoltsChanged); SubscribeLocalEvent(OnBeforeDoorOpened); SubscribeLocalEvent(OnBeforeDoorDenied); SubscribeLocalEvent(OnGetPryMod); SubscribeLocalEvent(OnBeforePry); SubscribeLocalEvent(OnSignalReceived); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnActivate, before: new[] { typeof(SharedDoorSystem) }); } private void OnBeforeDoorClosed(Entity ent, ref BeforeDoorClosedEvent args) { if (args.Cancelled) return; if (!ent.Comp.Safety) args.PerformCollisionCheck = false; // only block based on bolts / power status when initially closing the door, not when its already // mid-transition. Particularly relevant for when the door was pried-closed with a crowbar, which bypasses // the initial power-check. if (HasComp(ent) && !args.Partial && !CanChangeState(ent)) { args.Cancel(); } } private void OnStateChanged(Entity ent, ref DoorStateChangedEvent args) { // This is here so we don't accidentally bulldoze state values and mispredict. if (_timing.ApplyingState) return; // Only show the maintenance panel if the airlock is closed if (TryComp(ent, out var wiresPanel)) { _wiresSystem.ChangePanelVisibility(ent, wiresPanel, ent.Comp.OpenPanelVisible || args.State != DoorState.Open); } // If the door is closed, we should look if the bolt was locked while closing UpdateAutoClose((ent, ent.Comp)); // Make sure the airlock auto closes again next time it is opened if (args.State == DoorState.Closed) { ent.Comp.AutoClose = true; Dirty(ent); } } private void OnBoltsChanged(Entity ent, ref DoorBoltsChangedEvent args) { // If unbolted, reset the auto close timer if (!args.BoltsDown) UpdateAutoClose((ent, ent.Comp)); } private void OnBeforeDoorOpened(Entity ent, ref BeforeDoorOpenedEvent args) { if (!CanChangeState(ent)) args.Cancel(); } private void OnBeforeDoorDenied(Entity ent, ref BeforeDoorDeniedEvent args) { if (!CanChangeState(ent)) args.Cancel(); } private void OnGetPryMod(EntityUid uid, AirlockComponent component, ref GetPryTimeModifierEvent args) { if (component.Powered) args.PryTimeModifier *= component.PoweredPryModifier; if (DoorSystem.IsBolted(uid)) args.PryTimeModifier *= component.BoltedPryModifier; } /// /// Updates the auto close timer. /// public void UpdateAutoClose(Entity ent) { if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2)) return; if (ent.Comp2.State != DoorState.Open) return; if (!ent.Comp1.AutoClose) return; if (!CanChangeState((ent.Owner, ent.Comp1))) return; var autoev = new BeforeDoorAutoCloseEvent(); RaiseLocalEvent(ent, autoev); if (autoev.Cancelled) return; DoorSystem.SetNextStateChange(ent, ent.Comp1.AutoCloseDelay * ent.Comp1.AutoCloseDelayModifier); } private void OnBeforePry(Entity ent, ref BeforePryEvent args) { if (args.Cancelled) return; if (!ent.Comp.Powered || args.PryPowered) return; args.Message = ent.Comp.PryFailedPopup; args.Cancelled = true; } private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) { if (args.Port == ent.Comp.AutoClosePort && ent.Comp.AutoClose) { ent.Comp.AutoClose = false; Dirty(ent); } } private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) { ent.Comp.Powered = args.Powered; Dirty(ent); if (!TryComp(ent, out DoorComponent? door)) return; if (!args.Powered) { // stop any scheduled auto-closing if (door.State == DoorState.Open) DoorSystem.SetNextStateChange(ent, null); } else { UpdateAutoClose((ent, ent.Comp, door)); } } private void OnActivate(Entity ent, ref ActivateInWorldEvent args) { if (args.Handled || !args.Complex) return; if (ent.Comp.KeepOpenIfClicked && ent.Comp.AutoClose) { // Disable auto close ent.Comp.AutoClose = false; Dirty(ent); } } public void UpdateEmergencyLightStatus(Entity ent) { Appearance.SetData(ent, DoorVisuals.EmergencyLights, ent.Comp.EmergencyAccess); } public void SetEmergencyAccess(Entity ent, bool value, EntityUid? user = null, bool predicted = false) { if(!ent.Comp.Powered) return; if (ent.Comp.EmergencyAccess == value) return; ent.Comp.EmergencyAccess = value; Dirty(ent, ent.Comp); // This only runs on the server apparently so we need this. UpdateEmergencyLightStatus(ent); var sound = ent.Comp.EmergencyAccess ? ent.Comp.EmergencyOnSound : ent.Comp.EmergencyOffSound; if (predicted) Audio.PlayPredicted(sound, ent, user: user); else Audio.PlayPvs(sound, ent); } public void SetAutoCloseDelayModifier(AirlockComponent component, float value) { if (component.AutoCloseDelayModifier.Equals(value)) return; component.AutoCloseDelayModifier = value; } public void SetSafety(AirlockComponent component, bool value) { component.Safety = value; } public bool CanChangeState(Entity ent) { return ent.Comp.Powered && !DoorSystem.IsBolted(ent); } }