From 4a55a000cb316f4e120a1c7b1e9354d5a7a78d24 Mon Sep 17 00:00:00 2001 From: Tom Leys Date: Fri, 11 Aug 2023 21:29:33 +1200 Subject: [PATCH] Add Fire-fighting remote for Fire-doors (#16189) --- Content.Client/Doors/FirelockSystem.cs | 25 ++- .../Doors/Systems/FirelockSystem.cs | 9 +- Content.Server/Remotes/DoorRemoteSystem.cs | 28 ++- Content.Shared/Doors/DoorEvents.cs | 1 + .../Doors/Systems/SharedDoorBoltSystem.cs | 2 +- .../Doors/Systems/SharedDoorSystem.cs | 8 +- .../Catalog/Fills/Lockers/engineer.yml | 1 + .../Entities/Objects/Devices/door_remote.yml | 18 ++ .../Structures/Doors/Airlocks/highsec.yml | 188 +++++++++--------- .../Structures/Doors/Firelocks/firelock.yml | 3 + 10 files changed, 171 insertions(+), 112 deletions(-) diff --git a/Content.Client/Doors/FirelockSystem.cs b/Content.Client/Doors/FirelockSystem.cs index a06f3b8ff1..d4331fd15d 100644 --- a/Content.Client/Doors/FirelockSystem.cs +++ b/Content.Client/Doors/FirelockSystem.cs @@ -5,7 +5,7 @@ namespace Content.Client.Doors; public sealed class FirelockSystem : EntitySystem { - [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!; + [Dependency] protected readonly SharedAppearanceSystem _appearanceSystem = default!; public override void Initialize() { @@ -18,10 +18,23 @@ public sealed class FirelockSystem : EntitySystem if (args.Sprite == null) return; - // Apply the closed lights bool to the sprite - bool unlitVisible = - (AppearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && - closedLights); - args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible); + var boltedVisible = 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; + unlitVisible = + state == DoorState.Closing + || state == DoorState.Opening + || state == DoorState.Denying + || (_appearanceSystem.TryGetData(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights); + } + + args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && !boltedVisible); + args.Sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible); } } diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index df89680b69..7147aa4f24 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -4,7 +4,10 @@ using Content.Server.Atmos.Monitor.Systems; using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Remotes; using Content.Server.Shuttles.Components; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Doors; @@ -25,6 +28,7 @@ namespace Content.Server.Doors.Systems [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; private static float _visualUpdateInterval = 0.5f; private float _accumulatedFrameTime; @@ -133,7 +137,10 @@ namespace Content.Server.Doors.Systems private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) { - if (!this.IsPowered(uid, EntityManager) || IsHoldingPressureOrFire(uid, component)) + // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas + var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid); + + if (!this.IsPowered(uid, EntityManager) || (!overrideAccess && IsHoldingPressureOrFire(uid, component))) args.Cancel(); } diff --git a/Content.Server/Remotes/DoorRemoteSystem.cs b/Content.Server/Remotes/DoorRemoteSystem.cs index ad6b1b12ae..c63b2295c1 100644 --- a/Content.Server/Remotes/DoorRemoteSystem.cs +++ b/Content.Server/Remotes/DoorRemoteSystem.cs @@ -39,10 +39,14 @@ namespace Content.Server.Remotes component.Mode = OperatingMode.ToggleBolts; switchMessageId = "door-remote-switch-state-toggle-bolts"; break; + + // Skip toggle bolts mode and move on from there (to emergency access) case OperatingMode.ToggleBolts: component.Mode = OperatingMode.ToggleEmergencyAccess; switchMessageId = "door-remote-switch-state-toggle-emergency-access"; break; + + // Skip ToggleEmergencyAccess mode and move on from there (to door toggle) case OperatingMode.ToggleEmergencyAccess: component.Mode = OperatingMode.OpenClose; switchMessageId = "door-remote-switch-state-open-close"; @@ -56,15 +60,18 @@ namespace Content.Server.Remotes private void OnBeforeInteract(EntityUid uid, DoorRemoteComponent component, BeforeRangedInteractEvent args) { + bool isAirlock = TryComp(args.Target, out var airlockComp); + if (args.Handled || args.Target == null || !TryComp(args.Target, out var doorComp) // If it isn't a door we don't use it - || !TryComp(args.Target, out var airlockComp) // Remotes only work on airlocks // The remote can be used anywhere the user can see the door. // This doesn't work that well, but I don't know of an alternative || !_interactionSystem.InRangeUnobstructed(args.User, args.Target.Value, SharedInteractionSystem.MaxRaycastRange, CollisionGroup.Opaque)) + { return; + } args.Handled = true; @@ -74,8 +81,10 @@ namespace Content.Server.Remotes return; } - if (TryComp(args.Target, out var accessComponent) && - !_doorSystem.HasAccess(args.Target.Value, args.Used, doorComp, accessComponent)) + // Holding the door remote grants you access to the relevant doors IN ADDITION to what ever access you had. + // This access is enforced in _doorSystem.HasAccess when it calls _accessReaderSystem.IsAllowed + if (TryComp(args.Target, out var accessComponent) + && !_doorSystem.HasAccess(args.Target.Value, args.User, doorComp, accessComponent)) { _doorSystem.Deny(args.Target.Value, doorComp, args.User); ShowPopupToUser("door-remote-denied", args.User); @@ -85,7 +94,10 @@ namespace Content.Server.Remotes switch (component.Mode) { case OperatingMode.OpenClose: - if (_doorSystem.TryToggleDoor(args.Target.Value, doorComp, args.Used)) + // Note we provide args.User here to TryToggleDoor as the "user" + // This means that the door will look at all access items carryed by the player for access, including + // this remote, but also including anything else they are carrying such as a PDA or ID card. + if (_doorSystem.TryToggleDoor(args.Target.Value, doorComp, args.User)) _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)}: {doorComp.State}"); break; case OperatingMode.ToggleBolts: @@ -99,8 +111,12 @@ namespace Content.Server.Remotes } break; case OperatingMode.ToggleEmergencyAccess: - _airlock.ToggleEmergencyAccess(args.Target.Value, airlockComp); - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}"); + if (airlockComp != null) + { + _airlock.ToggleEmergencyAccess(args.Target.Value, airlockComp); + _adminLogger.Add(LogType.Action, LogImpact.Medium, + $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to set emergency access {(airlockComp.EmergencyAccess ? "on" : "off")}"); + } break; default: throw new InvalidOperationException( diff --git a/Content.Shared/Doors/DoorEvents.cs b/Content.Shared/Doors/DoorEvents.cs index 22c684c75a..5b0ca71ede 100644 --- a/Content.Shared/Doors/DoorEvents.cs +++ b/Content.Shared/Doors/DoorEvents.cs @@ -21,6 +21,7 @@ namespace Content.Shared.Doors /// public sealed class BeforeDoorOpenedEvent : CancellableEntityEventArgs { + public EntityUid? User = null; } /// diff --git a/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs b/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs index ed1e9d4351..e8be596b06 100644 --- a/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorBoltSystem.cs @@ -8,7 +8,7 @@ public abstract class SharedDoorBoltSystem : EntitySystem [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; - [Dependency] private readonly SharedPopupSystem Popup = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; public override void Initialize() { base.Initialize(); diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index 64e6d7a8cd..b5528ea0d2 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -249,7 +249,7 @@ public abstract class SharedDoorSystem : EntitySystem if (door.State == DoorState.Welded) return false; - var ev = new BeforeDoorOpenedEvent(); + var ev = new BeforeDoorOpenedEvent(){User=user}; RaiseLocalEvent(uid, ev, false); if (ev.Cancelled) return false; @@ -496,10 +496,10 @@ public abstract class SharedDoorSystem : EntitySystem if (TryComp(uid, out var airlock) && airlock.EmergencyAccess) return true; - // Can't click to close firelocks. - if (Resolve(uid, ref door) && door.State == DoorState.Open && + // Anyone can click to open firelocks + if (Resolve(uid, ref door) && door.State == DoorState.Closed && TryComp(uid, out var firelock)) - return false; + return true; if (!Resolve(uid, ref access, false)) return true; diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml index 00c368eb0a..c140ba3290 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml @@ -89,6 +89,7 @@ - id: GasAnalyzer - id: MedkitOxygenFilled - id: HolofanProjector + - id: DoorRemoteFirefight - type: entity id: LockerAtmosphericsFilled diff --git a/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml b/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml index 58331a36e3..32cd3dba88 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/door_remote.yml @@ -138,6 +138,24 @@ groups: - Engineering +- type: entity + parent: DoorRemoteDefault + id: DoorRemoteFirefight + name: fire-fighting door remote + description: A gadget which can open and bolt FireDoors remotely. + components: + - type: Sprite + layers: + - state: door_remotebase + - state: door_remotelightscolour + color: "#ff9900" + - state: door_remotescreencolour + color: "#e02020" + + - type: Access + groups: + - Engineering + - type: entity parent: DoorRemoteDefault id: DoorRemoteAll diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml index e408b75f07..7b44c00f7a 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml @@ -1,94 +1,94 @@ -- type: entity - id: HighSecDoor - parent: BaseStructure - name: high security door - description: Keeps the bad out and keeps the good in. - placement: - mode: SnapgridCenter - components: - - type: InteractionOutline - - type: Sprite - sprite: Structures/Doors/Airlocks/highsec/highsec.rsi - layers: - - state: closed - map: ["enum.DoorVisualLayers.Base"] - - state: closed_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseUnlit"] - - state: welded - map: ["enum.WeldableLayers.BaseWelded"] - - state: bolted_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseBolted"] - - state: emergency_unlit - map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - shader: unshaded - - state: panel_open - map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: AnimationPlayer - - type: Physics - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeAabb - bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close - density: 100 - mask: - - FullTileMask - layer: - - WallLayer - - type: ContainerFill - containers: - board: [ DoorElectronics ] - - type: ContainerContainer - containers: - board: !type:Container - - type: Door - crushDamage: - types: - Blunt: 50 - openSound: - path: /Audio/Machines/airlock_open.ogg - closeSound: - path: /Audio/Machines/airlock_close.ogg - denySound: - path: /Audio/Machines/airlock_deny.ogg - - type: Weldable - time: 10 - - type: Airlock - - type: DoorBolt - - type: Appearance - - type: WiresVisuals - - type: ApcPowerReceiver - powerLoad: 20 - - type: ExtensionCableReceiver - - type: Electrified - enabled: false - usesApcPower: true - - type: WiresPanel - - type: Wires - BoardName: "HighSec Control" - LayoutId: HighSec - alwaysRandomize: true - - type: UserInterface - interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface - - type: Airtight - fixVacuum: true - - type: Occluder - - type: Damageable - damageContainer: Inorganic - damageModifierSet: Metallic - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 1500 - behaviors: - - !type:DoActsBehavior - acts: ["Destruction"] - - type: IconSmooth - key: walls - mode: NoSprite +- type: entity + id: HighSecDoor + parent: BaseStructure + name: high security door + description: Keeps the bad out and keeps the good in. + placement: + mode: SnapgridCenter + components: + - type: InteractionOutline + - type: Sprite + sprite: Structures/Doors/Airlocks/highsec/highsec.rsi + layers: + - state: closed + map: ["enum.DoorVisualLayers.Base"] + - state: closed_unlit + shader: unshaded + map: ["enum.DoorVisualLayers.BaseUnlit"] + - state: welded + map: ["enum.WeldableLayers.BaseWelded"] + - state: bolted_unlit + shader: unshaded + map: ["enum.DoorVisualLayers.BaseBolted"] + - state: emergency_unlit + map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] + shader: unshaded + - state: panel_open + map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: AnimationPlayer + - type: Physics + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close + density: 100 + mask: + - FullTileMask + layer: + - WallLayer + - type: ContainerFill + containers: + board: [ DoorElectronics ] + - type: ContainerContainer + containers: + board: !type:Container + - type: Door + crushDamage: + types: + Blunt: 50 + openSound: + path: /Audio/Machines/airlock_open.ogg + closeSound: + path: /Audio/Machines/airlock_close.ogg + denySound: + path: /Audio/Machines/airlock_deny.ogg + - type: Weldable + time: 10 + - type: Airlock + - type: DoorBolt + - type: Appearance + - type: WiresVisuals + - type: ApcPowerReceiver + powerLoad: 20 + - type: ExtensionCableReceiver + - type: Electrified + enabled: false + usesApcPower: true + - type: WiresPanel + - type: Wires + BoardName: "HighSec Control" + LayoutId: HighSec + alwaysRandomize: true + - type: UserInterface + interfaces: + - key: enum.WiresUiKey.Key + type: WiresBoundUserInterface + - type: Airtight + fixVacuum: true + - type: Occluder + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 1500 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + - type: IconSmooth + key: walls + mode: NoSprite diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 32a6804d34..b26006344d 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -104,6 +104,9 @@ arc: 360 - type: StaticPrice price: 150 + - type: DoorBolt + - type: AccessReader + access: [ [ "Engineering" ] ] - type: entity id: Firelock