Powerless airlocks - Closed by default and the emergency override (#21649)

# Summary

This PR updates the default behavior of airlocks on powerloss and adds a
manual override for them.
By default airlocks now keep closed (and close if opened) on powerloss.
Additionally a new mechanic has been added allowing people to manually
open unpowered, unbolted and unsecure airlocks without power by
utilizing a manual override.

## Mechanics

- On power loss doors will stay closed.
- On power loss opened doors will now close.
- Manual override mechanic:
If a door is functional but unpowered and features an override handle
(previously called "insecure" by default, false for vaults and similar)
the user can now engage the manual override. It works by cranking a
hydraulic lever multiple times to force open the door without a crowbar,
this process takes around 15 seconds. The process is divided into
3-second steps each given a ratchet sound. When user waits long enough
the door will open. Doors that have been opened with the manual override
will automatically close again after the door receives power again, just
like the old behavior. While waiting the user will be informed about the
length of the process by seeing a small oil gauge fill up (via chat
message).

Process:
- User clicked unpowered door
- 3 second action (including a ratchet sound) x 5
- Every action feedback is given:
<img width="784" height="169" alt="image"
src="https://github.com/user-attachments/assets/4cf940bb-458b-4bce-8893-3cd96b5b2455"
/>

<hr>

Idea originated from: [Forum
thread](https://forums.aurorastation.org/topic/22728-airlocks-opening-on-powerloss/#findComment-182862)

## Changes

- Renamed `insecure` airlock feature to
`features_powerloss_manual_override`.
- Adjusted default powerloss behavior to close instead to open, if not
already closed.
- Added manual override feature.
- Update all `insecure = FALSE` references to
`features_powerloss_manual_override = FALSE` (vaults and similar
locations).
- Fixed a CI issue with a `RegisterSignal`.
This commit is contained in:
FabianK3
2025-12-19 21:21:16 +01:00
committed by GitHub
parent 8ebc0b5213
commit 068352fa46
8 changed files with 124 additions and 74 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
. = ..()
if(!ismovable(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_target_move))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_target_move), TRUE)
var/atom/movable/movable_target = target
if(!isturf(movable_target.loc))
return
+63 -20
View File
@@ -158,9 +158,12 @@
var/datum/wifi/receiver/button/door/wifi_receiver
var/has_set_boltlight = FALSE
/// Boolean. If TRUE, the door will open when power runs out.
var/insecure = TRUE
var/securitylock = FALSE
/// Boolean. If true the door features an override handle allowing to open the door when the power is out.
var/features_powerloss_manual_override = TRUE
/// Boolean. Internal state set after the no power manual override has been used. If true (set after usage) the door will close on receiving power again.
var/revert_powerloss_manual_override = FALSE
/// Soundfile. The sound played when the no power manual override is used.
var/powerless_manual_override_sound = 'sound/items/Ratchet.ogg'
/// Override access by code level. This is an associative list that should only be set through /obj/effect/map_effect/door_helper/level_access.
/// Structure example: "red" -> list(1, 2)
@@ -175,6 +178,9 @@
. += "Airlocks use access control; you must be wearing your ID (or an object containing your ID) in your ID slot, wrist slot, or active in-hand, for it to be read."
. += "Airlocks require power to function. When power is lost, an airlock might fail closed or open, depending on how secure it is."
. += "An unpowered airlock can be opened or closed with a crowbar, but a powered airlock cannot."
if(features_powerloss_manual_override)
. += "This door specifically features a manual override when it's unpowered. Tap the door with an empty hand to attempt to override it when it is unpowered."
. += "Note that the door needs to be otherwise functional and unbolted. Not every airlock has this override."
/obj/machinery/door/airlock/antagonist_hints(mob/user, distance, is_adjacent)
. += ..()
@@ -314,7 +320,7 @@
door_frame_color = "#81838b"//Meant to connect to external scc spaceship walls like the horizon hull
door_color = "#813c3c"
hashatch = FALSE
insecure = 0
features_powerloss_manual_override = FALSE
fill_file = 'icons/obj/doors/basic/single/external/fill_steel.dmi'
color_file = 'icons/obj/doors/basic/single/external/color.dmi'
color_fill_file = 'icons/obj/doors/basic/single/external/fill_color.dmi'
@@ -407,7 +413,7 @@
icon = 'icons/obj/doors/basic/single/external/door.dmi'
assembly_type = /obj/structure/door_assembly/door_assembly_ext
hashatch = FALSE
insecure = 0
features_powerloss_manual_override = FALSE
ai_control_disabled = 1
hack_proof = TRUE
open_sound_powered = 'sound/machines/airlock/space1o.ogg'
@@ -527,7 +533,7 @@
explosion_resistance = 20
secured_wires = TRUE
maxhealth = 600
insecure = 0
features_powerloss_manual_override = FALSE
ai_bolting_delay = 10
ai_unbolt_delay = 5
open_sound_powered = 'sound/machines/airlock/secure1o.ogg'
@@ -560,7 +566,7 @@
door_color = "#4e5170"
stripe_color = "#ffc443"
assembly_type = /obj/structure/door_assembly/door_assembly_hatch
insecure = 0
features_powerloss_manual_override = FALSE
open_sound_powered = 'sound/machines/airlock/hatchopen.ogg'
close_sound_powered = 'sound/machines/airlock/hatchclose.ogg'
open_sound_unpowered = 'sound/machines/airlock/hatchforced.ogg'
@@ -754,7 +760,7 @@
secured_wires = TRUE
assembly_type = /obj/structure/door_assembly/door_assembly_highsecurity
maxhealth = 600
insecure = 0
features_powerloss_manual_override = FALSE
ai_bolting_delay = 10
ai_unbolt_delay = 5
open_sound_powered = 'sound/machines/airlock/secure1o.ogg'
@@ -766,7 +772,7 @@
explosion_resistance = 20
secured_wires = TRUE
maxhealth = 600
insecure = 0
features_powerloss_manual_override = FALSE
hashatch = FALSE
/obj/machinery/door/airlock/diona
@@ -776,7 +782,7 @@
explosion_resistance = 20
secured_wires = TRUE
maxhealth = 600
insecure = FALSE
features_powerloss_manual_override = FALSE
hashatch = FALSE
/// Placeholder object until it gets new sprites.
@@ -1433,13 +1439,53 @@ About the new airlock wires panel:
take_damage(H.default_attack.attack_door)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
return
if(src.p_open)
user.set_machine(src)
wires.interact(user)
else
..(user)
return
if((ishuman(user) || istype(usr, /mob/living/silicon)) && src.density && (stat & NOPOWER) && user.Adjacent(src))
user.visible_message(
SPAN_NOTICE("<b>[user]</b> attempts to open \the [src]."),
SPAN_ITALIC("It seems like the door isn't opening. You try to locate and unlock the manual override...")
)
if(do_after(user, 5 SECONDS, src, DO_UNIQUE))
if(!features_powerloss_manual_override)
to_chat(user, SPAN_WARNING("This door doesn't seem to have a manual override..."))
return
if(locked || (stat & BROKEN))
to_chat(user, SPAN_WARNING("The override handle doesn't move an inch..."))
return
user.visible_message(
SPAN_WARNING("<b>[user]</b> begins to crank the manual override handle of \the [src]!"),
SPAN_NOTICE("You start to crank the manual override lever of \the [src]! This might take a moment..."),
SPAN_WARNING("You hear the sound of a ratchet being used!")
)
powerless_manual_override_procedure(user)
return
..(user)
return
/obj/machinery/door/airlock/proc/powerless_manual_override_procedure(var/mob/user, var/current_iteration = 0)
current_iteration++
if(operable() || !src.density || locked)
to_chat(user, SPAN_WARNING("You suddenly hear something moving in \the [src]! You stop cranking the handle..."))
return
else if(current_iteration <= 5) // It takes a couple of steps to finish the procedure: 20%-40%-60%-80%-100%
playsound(loc, powerless_manual_override_sound, 20, FALSE)
if (do_after(user, 3 SECONDS, src, DO_UNIQUE))
to_chat(user, SPAN_NOTICE("The small oil gauge next to the handle raises to the [20 * current_iteration]% mark."))
powerless_manual_override_procedure(user, current_iteration)
else if(current_iteration > 5)
to_chat(user, SPAN_NOTICE("The small oil gauge next to the handle raises to the 100% mark."))
to_chat(user, SPAN_WARNING("The oil gauge suddenly drops to zero and the door opens!"))
playsound(src.loc, open_sound_unpowered, 70, FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
src.open(1)
revert_powerloss_manual_override = TRUE
//returns 1 on success, 0 on failure
/obj/machinery/door/airlock/proc/cut_bolts(var/obj/item/tool, var/mob/user)
var/cut_delay = 200
@@ -2106,16 +2152,13 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/power_change() //putting this is obj/machinery/door itself makes non-airlock doors turn invisible for some reason
..()
if(stat & NOPOWER)
// If we lost power, disable electrification
// Keeping door lights on, runs on internal battery or something.
// The door lost power - Disable electrification and close it.
electrified_until = 0
//if we lost power open 'er up
if(insecure)
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/door, open), 1)
securitylock = TRUE
else if(securitylock)
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/door, close), 1)
securitylock = FALSE
else if(revert_powerloss_manual_override)
// The door regained power and the powerless override was used - Close the door again.
revert_powerloss_manual_override = FALSE
INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/door, close), 1)
update_icon()
/obj/machinery/door/airlock/proc/prison_open()
+1 -1
View File
@@ -10,7 +10,7 @@
autoclose = FALSE
hashatch = FALSE
panel_visible_while_open = TRUE
insecure = FALSE
features_powerloss_manual_override = FALSE
var/datum/turbolift/lift
var/datum/turbolift_floor/floor
@@ -0,0 +1,7 @@
author: FabianK3
delete-after: True
changes:
- qol: "Updated airlock behavior on power loss. Airlocks will no longer open when they lose power but instead will stay closed or will close if already open."
- rscadd: "Added airlock manual override mechanic. When an airlock is functional and unbolted but simply has no power, a manual override can be used to open it. Click the unpowered airlock with an empty hand to start the override procedure. This process takes around fifteen seconds and will force open the airlock without requiring any tool. It's designed for emergency cases when power failed. The door will close again after regaining power after the emergency override has been used. Secure airlocks do not feature the manual override."
+15 -15
View File
@@ -396,7 +396,7 @@
/area/magshield/smes_storage)
"bg" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/turf/simulated/floor/tiled/white,
@@ -1036,7 +1036,7 @@
"cS" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
@@ -1064,7 +1064,7 @@
"cU" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/structure/cable{
@@ -1266,7 +1266,7 @@
/area/magshield/north)
"dB" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
@@ -1388,7 +1388,7 @@
/area/magshield/north)
"ee" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/turf/simulated/floor/tiled/white,
@@ -1862,7 +1862,7 @@
icon_state = "1-2"
},
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/turf/simulated/floor/airless,
@@ -2298,7 +2298,7 @@
dir = 4
},
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/obj/structure/cable{
@@ -2589,7 +2589,7 @@
"hH" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
@@ -2758,7 +2758,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/structure/cable{
@@ -3766,7 +3766,7 @@
"os" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
@@ -3852,7 +3852,7 @@
/area/magshield/south)
"yw" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
@@ -3888,7 +3888,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/structure/cable{
@@ -3901,7 +3901,7 @@
"Ch" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
@@ -3991,7 +3991,7 @@
dir = 4
},
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/turf/simulated/floor/tiled,
@@ -4110,7 +4110,7 @@
/area/magshield/north)
"VU" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/obj/structure/cable{
+30 -30
View File
@@ -319,7 +319,7 @@
"be" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Casino Bridge";
dir = 1
},
@@ -334,7 +334,7 @@
"bf" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Casino Bridge";
dir = 1
},
@@ -524,7 +524,7 @@
/area/casino/casino_maintenance)
"bG" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Security wing";
dir = 4
},
@@ -597,7 +597,7 @@
/area/casino/casino_security)
"bM" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Shuttle control";
dir = 4
},
@@ -920,7 +920,7 @@
"cJ" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Crew toilet";
dir = 4
},
@@ -1034,7 +1034,7 @@
"db" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Crew bunk room";
dir = 4
},
@@ -1152,7 +1152,7 @@
/area/casino/casino_crew_bathroom)
"du" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Crew toilet";
dir = 4
},
@@ -1679,7 +1679,7 @@
"ff" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 1
},
@@ -1695,7 +1695,7 @@
/area/casino/casino_maintenance)
"fg" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Security wing";
dir = 1
},
@@ -1706,7 +1706,7 @@
"fh" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Shuttle hangar";
dir = 1
},
@@ -1752,7 +1752,7 @@
/area/casino/casino_crew_atmos)
"fo" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Main desk";
dir = 4
},
@@ -1788,7 +1788,7 @@
/area/casino/casino_security)
"fr" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Shuttle checkpoint";
dir = 4
},
@@ -1879,7 +1879,7 @@
"fE" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 1
},
@@ -1892,7 +1892,7 @@
/area/casino/casino_maintenance)
"fF" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Main desk";
dir = 1
},
@@ -2052,7 +2052,7 @@
"gd" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Casino";
dir = 1
},
@@ -2484,7 +2484,7 @@
"hx" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 4
},
@@ -2618,7 +2618,7 @@
"hT" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 4
},
@@ -2715,7 +2715,7 @@
/obj/machinery/door/firedoor,
/obj/machinery/atmospherics/pipe/simple/hidden,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Solar control";
dir = 1
},
@@ -2729,7 +2729,7 @@
"ii" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Atmos control";
dir = 1
},
@@ -3159,7 +3159,7 @@
"jl" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Bar storage";
dir = 4
},
@@ -3195,7 +3195,7 @@
"jo" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Bar";
dir = 4
},
@@ -3570,7 +3570,7 @@
"kn" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 1
},
@@ -4050,7 +4050,7 @@
"lt" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Toilet room";
dir = 1
},
@@ -4163,7 +4163,7 @@
/area/casino/casino_patron_bathroom)
"lS" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Toilet #1";
dir = 4
},
@@ -4194,7 +4194,7 @@
"lX" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Bow";
dir = 1
},
@@ -4370,7 +4370,7 @@
/area/casino/casino_patron_bathroom)
"mC" = (
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Toilet #2";
dir = 4
},
@@ -4419,7 +4419,7 @@
"mL" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Bow";
dir = 1
},
@@ -4500,7 +4500,7 @@
"mY" = (
/obj/machinery/atmospherics/pipe/simple/visible/black,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Engines #1";
dir = 1
},
@@ -4528,7 +4528,7 @@
"nb" = (
/obj/machinery/atmospherics/pipe/simple/visible/black,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Engines #2";
dir = 1
},
@@ -5040,7 +5040,7 @@
"zc" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock{
insecure = 0;
features_powerloss_manual_override = 0;
name = "Kitchen";
dir = 4
},
+6 -6
View File
@@ -121,7 +121,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/turf/simulated/floor/wood/walnut,
@@ -220,7 +220,7 @@
/area/shuttle/yacht/living)
"aP" = (
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/turf/simulated/floor/wood/walnut,
@@ -274,7 +274,7 @@
/obj/structure/closet/walllocker/emerglocker/north,
/obj/effect/decal/cleanable/blood/drip,
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/turf/simulated/floor/wood/walnut,
@@ -381,7 +381,7 @@
/area/shuttle/yacht/living)
"bo" = (
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/turf/simulated/floor/tiled/freezer,
@@ -463,7 +463,7 @@
dir = 4
},
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 4
},
/turf/simulated/floor/carpet/purple,
@@ -546,7 +546,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
/obj/effect/decal/cleanable/blood/gibs/robot/down,
/obj/machinery/door/airlock/silver{
insecure = 0;
features_powerloss_manual_override = 0;
dir = 1
},
/turf/simulated/floor/wood/yew,
@@ -111,7 +111,7 @@
},
/obj/machinery/door/airlock/multi_tile{
name = "Cockpit";
insecure = 0
features_powerloss_manual_override = 0
},
/turf/simulated/floor/tiled/slate,
/area/shuttle/kazhkz_crash)