From c4877abb6f97a3c182225a6770cb46e09ccfb26a Mon Sep 17 00:00:00 2001 From: LT3 <83487515+lessthnthree@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:31:26 -0700 Subject: [PATCH] Airlock variable door delay fixes (#91828) ## About The Pull Request - Fixes airlock's run_animation from having a duplicate open in the switch case instead of an open/closed. - Completes the implementation of [Variable Door Delay](https://github.com/tgstation/tgstation/pull/84631) on airlocks. (The procs were made but never called in the airlock code. - Implements a set_airlock_state that sets the airlock state as well as managing operating status and animations instead of calling it individually. ## Why It's Good For The Game Fixes, less code duplication, airlocks use the new animation procs. ## Changelog :cl: LT3 fix: Fixed incorrect opening case in variable door delay code: Airlocks can have variable animation delay same as doors /:cl: --- code/__DEFINES/airlock.dm | 11 ++- code/game/machinery/doors/airlock.dm | 95 +++++++++++++---------- code/game/machinery/doors/door.dm | 10 +-- code/modules/transport/tram/tram_doors.dm | 64 ++++++++++----- 4 files changed, 107 insertions(+), 73 deletions(-) diff --git a/code/__DEFINES/airlock.dm b/code/__DEFINES/airlock.dm index dd3ee6a8ecb..3a7113b5d7f 100644 --- a/code/__DEFINES/airlock.dm +++ b/code/__DEFINES/airlock.dm @@ -6,9 +6,8 @@ #define AIRLOCK_LIGHT_OPENING "opening" // Airlock physical states -#define AIRLOCK_CLOSED 1 -#define AIRLOCK_CLOSING 2 -#define AIRLOCK_OPEN 3 -#define AIRLOCK_OPENING 4 -#define AIRLOCK_DENY 5 -#define AIRLOCK_EMAG 6 +#define AIRLOCK_CLOSED "closed" +#define AIRLOCK_CLOSING "closing" +#define AIRLOCK_OPEN "open" +#define AIRLOCK_OPENING "opening" +#define AIRLOCK_DENY "deny" diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index a1ef9687aae..0a7de6881c4 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -76,6 +76,7 @@ name = "Airlock" icon = 'icons/obj/doors/airlocks/station/public.dmi' icon_state = "closed" + base_icon_state = null max_integrity = 300 var/normal_integrity = AIRLOCK_INTEGRITY_N integrity_failure = 0.25 @@ -502,30 +503,44 @@ /obj/machinery/door/airlock/proc/is_secure() return (security_level > 0) -/obj/machinery/door/airlock/update_icon(updates=ALL, state=0, override=FALSE) - if(operating && !override) +/** + * Set the airlock state to a new value, change the icon state + * and run the associated animation if required. + */ +/obj/machinery/door/airlock/proc/set_airlock_state(new_state, animated = FALSE) + if(!new_state) + new_state = density ? AIRLOCK_CLOSED : AIRLOCK_OPEN + airlock_state = new_state + if(animated) + operating = TRUE + run_animation(airlock_state) return + operating = FALSE + set_animation() - if(!state) - state = density ? AIRLOCK_CLOSED : AIRLOCK_OPEN - airlock_state = state +/obj/machinery/door/airlock/update_icon(updates = ALL) + if(!airlock_state) + airlock_state = icon_state - . = ..() + return ..() /obj/machinery/door/airlock/update_icon_state() . = ..() - switch(airlock_state) - if(AIRLOCK_OPEN, AIRLOCK_CLOSED) - icon_state = "" - if(AIRLOCK_DENY, AIRLOCK_OPENING, AIRLOCK_CLOSING, AIRLOCK_EMAG) - icon_state = "nonexistenticonstate" //MADNESS + if(animation) + icon_state = "[base_icon_state][animation]" + else if(airlock_state == AIRLOCK_OPEN) + icon_state = "[base_icon_state]open" + else + icon_state = "[base_icon_state]closed" /obj/machinery/door/airlock/update_overlays() . = ..() var/frame_state var/light_state - switch(airlock_state) + if(machine_stat & MAINT) // in the process of being emagged + frame_state = AIRLOCK_FRAME_CLOSED + else switch(airlock_state) if(AIRLOCK_CLOSED) frame_state = AIRLOCK_FRAME_CLOSED if(locked) @@ -535,8 +550,6 @@ if(AIRLOCK_DENY) frame_state = AIRLOCK_FRAME_CLOSED light_state = AIRLOCK_LIGHT_DENIED - if(AIRLOCK_EMAG) - frame_state = AIRLOCK_FRAME_CLOSED if(AIRLOCK_CLOSING) frame_state = AIRLOCK_FRAME_CLOSING light_state = AIRLOCK_LIGHT_CLOSING @@ -557,10 +570,11 @@ if(panel_open) . += get_airlock_overlay("panel_[frame_state][security_level ? "_protected" : null]", overlays_file, src, em_block = TRUE) + if(frame_state == AIRLOCK_FRAME_CLOSED && welded) . += get_airlock_overlay("welded", overlays_file, src, em_block = TRUE) - if(airlock_state == AIRLOCK_EMAG) + if(machine_stat & MAINT) // in the process of being emagged . += get_airlock_overlay("sparks", overlays_file, src, em_block = FALSE) if(hasPower()) @@ -600,20 +614,21 @@ . += floorlight /obj/machinery/door/airlock/run_animation(animation) - switch(animation) - if(DOOR_OPENING_ANIMATION) - update_icon(ALL, AIRLOCK_OPENING) - if(DOOR_OPENING_ANIMATION) - update_icon(ALL, AIRLOCK_CLOSING) - if(DOOR_DENY_ANIMATION) - if(!machine_stat) - update_icon(ALL, AIRLOCK_DENY) - playsound(src,doorDeni,50,FALSE,3) - addtimer(CALLBACK(src, PROC_REF(handle_deny_end)), AIRLOCK_DENY_ANIMATION_TIME) + if(animation == DOOR_DENY_ANIMATION) + if(machine_stat) + return + set_airlock_state(AIRLOCK_DENY, animated = FALSE) + + return ..() + +/obj/machinery/door/airlock/animation_effects(animation) + if(animation == DOOR_DENY_ANIMATION) + playsound(src, soundin = doorDeni, vol = 50, vary = FALSE, extrarange = 3) + addtimer(CALLBACK(src, PROC_REF(handle_deny_end)), AIRLOCK_DENY_ANIMATION_TIME) /obj/machinery/door/airlock/proc/handle_deny_end() if(airlock_state == AIRLOCK_DENY) - update_icon(ALL, AIRLOCK_CLOSED) + set_airlock_state(AIRLOCK_CLOSED, animated = FALSE) /obj/machinery/door/airlock/animation_length(animation) switch(animation) @@ -1279,8 +1294,7 @@ addtimer(CALLBACK(cyclelinkedairlock, PROC_REF(close)), BYPASS_DOOR_CHECKS) SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, forced) - operating = TRUE - update_icon(ALL, AIRLOCK_OPENING, TRUE) + set_airlock_state(AIRLOCK_OPENING, animated = TRUE) var/transparent_delay = animation_segment_delay(AIRLOCK_OPENING_TRANSPARENT) sleep(transparent_delay) set_opacity(0) @@ -1297,8 +1311,7 @@ var/open_delay = animation_segment_delay(AIRLOCK_OPENING_FINISHED) - transparent_delay - passable_delay sleep(open_delay) layer = OPEN_DOOR_LAYER - update_icon(ALL, AIRLOCK_OPEN, TRUE) - operating = FALSE + set_airlock_state(AIRLOCK_OPEN, animated = FALSE) if(delayed_close_requested) delayed_close_requested = FALSE addtimer(CALLBACK(src, PROC_REF(close)), FORCING_DOOR_CHECKS) @@ -1355,8 +1368,7 @@ if(killthis) SSexplosions.med_mov_atom += killthis SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE, forced) - operating = TRUE - update_icon(ALL, AIRLOCK_CLOSING, 1) + set_airlock_state(AIRLOCK_CLOSING, animated = TRUE) layer = CLOSED_DOOR_LAYER if(air_tight) set_density(TRUE) @@ -1383,8 +1395,7 @@ update_freelook_sight() var/close_delay = animation_segment_delay(AIRLOCK_CLOSING_FINISHED) - unpassable_delay - opaque_delay sleep(close_delay) - update_icon(ALL, AIRLOCK_CLOSED, 1) - operating = FALSE + set_airlock_state(AIRLOCK_CLOSED, animated = FALSE) delayed_close_requested = FALSE if(!dangerous_close) CheckForMobs() @@ -1457,8 +1468,9 @@ if(istype(emag_card, /obj/item/card/emag/doorjack)) var/obj/item/card/emag/doorjack/doorjack_card = emag_card doorjack_card.use_charge(user) + set_machine_stat(machine_stat | MAINT) // flash the airlock lights and display some sparks + set_airlock_state(AIRLOCK_CLOSED) operating = TRUE - update_icon(ALL, AIRLOCK_EMAG, 1) addtimer(CALLBACK(src, PROC_REF(finish_emag_act)), 0.6 SECONDS) return TRUE return FALSE @@ -1467,9 +1479,10 @@ /obj/machinery/door/airlock/proc/finish_emag_act() if(QDELETED(src)) return FALSE + set_machine_stat(machine_stat & ~MAINT) operating = FALSE if(!open()) - update_icon(ALL, AIRLOCK_CLOSED, 1) + set_airlock_state(AIRLOCK_CLOSED) obj_flags |= EMAGGED lights = FALSE locked = TRUE @@ -2529,19 +2542,17 @@ // set_density on both open and close procs has a check and return builtin. /obj/machinery/door/airlock/instant/open(forced = DEFAULT_DOOR_CHECKS) - operating = TRUE SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, forced) + operating = TRUE set_density(FALSE) - operating = FALSE - update_appearance() + set_airlock_state(AIRLOCK_OPEN, animated = FALSE) return TRUE /obj/machinery/door/airlock/instant/close(forced = DEFAULT_DOOR_CHECKS, force_crush = FALSE) - operating = TRUE SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE, forced) + operating = TRUE set_density(TRUE) - operating = FALSE - update_appearance() + set_airlock_state(AIRLOCK_CLOSED, animated = FALSE) return TRUE /obj/machinery/door/airlock/instant/glass diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index bb4d3de0b69..60130bbfb8c 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -436,17 +436,17 @@ switch(animation) if(DOOR_OPENING_ANIMATION) if(panel_open) - icon_state = "o_door_opening" + icon_state = "o_[base_icon_state]_opening" else - icon_state = "door_opening" + icon_state = "[base_icon_state]_opening" if(DOOR_CLOSING_ANIMATION) if(panel_open) - icon_state = "o_door_closing" + icon_state = "o_[base_icon_state]_closing" else - icon_state = "door_closing" + icon_state = "[base_icon_state]_closing" if(DOOR_DENY_ANIMATION) if(!machine_stat) - icon_state = "door_deny" + icon_state = "[base_icon_state]_deny" else icon_state = "[base_icon_state]_[density ? "closed" : "open"]" diff --git a/code/modules/transport/tram/tram_doors.dm b/code/modules/transport/tram/tram_doors.dm index 0462fae7c53..5f902497803 100644 --- a/code/modules/transport/tram/tram_doors.dm +++ b/code/modules/transport/tram/tram_doors.dm @@ -30,36 +30,37 @@ id_tag = assign_random_name() /obj/machinery/door/airlock/tram/open(forced = DEFAULT_DOOR_CHECKS) - if(operating || welded || locked || seal) + if(welded || locked || seal) return FALSE if(!density) return TRUE - if(forced == DEFAULT_DOOR_CHECKS && (!hasPower() || wires.is_cut(WIRE_OPEN))) + if(forced == DEFAULT_DOOR_CHECKS && (operating || !hasPower() || wires.is_cut(WIRE_OPEN))) return FALSE SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN, FALSE) - operating = TRUE - update_icon(ALL, AIRLOCK_OPENING, TRUE) + set_airlock_state(AIRLOCK_OPENING, animated = TRUE) + var/passable_delay if(forced >= BYPASS_DOOR_CHECKS) playsound(src, 'sound/machines/airlock/airlockforced.ogg', vol = 40, vary = FALSE) - sleep(TRAM_DOOR_CYCLE_TIME) + passable_delay = 0 else playsound(src, doorOpen, vol = 40, vary = FALSE) - sleep(TRAM_DOOR_WARNING_TIME) + passable_delay = animation_segment_delay(AIRLOCK_OPENING_PASSABLE) + sleep(passable_delay) set_density(FALSE) if(!isnull(filler)) filler.set_density(FALSE) update_freelook_sight() flags_1 &= ~PREVENT_CLICK_UNDER_1 air_update_turf(TRUE, FALSE) - sleep(TRAM_DOOR_WARNING_TIME) + var/open_delay = animation_segment_delay(AIRLOCK_OPENING_FINISHED) - passable_delay + sleep(open_delay) layer = OPEN_DOOR_LAYER - update_icon(ALL, AIRLOCK_OPEN, TRUE) - operating = FALSE + set_airlock_state(AIRLOCK_OPEN, animated = FALSE) return TRUE @@ -93,10 +94,10 @@ playsound(src, SFX_SPARKS, vol = 75, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) use_energy(50 JOULES) playsound(src, doorClose, vol = 40, vary = FALSE) - operating = TRUE layer = CLOSED_DOOR_LAYER - update_icon(ALL, AIRLOCK_CLOSING, 1) - sleep(TRAM_DOOR_WARNING_TIME) + set_airlock_state(AIRLOCK_CLOSING, animated = TRUE) + var/unpassable_delay = animation_segment_delay(AIRLOCK_CLOSING_UNPASSABLE) + sleep(unpassable_delay) if(!hungry_door) for(var/turf/checked_turf in locs) for(var/atom/movable/blocker in checked_turf) @@ -104,11 +105,11 @@ say("Please stand clear of the doors!") playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) layer = OPEN_DOOR_LAYER - update_icon(ALL, AIRLOCK_OPEN, 1) - operating = FALSE + set_airlock_state(AIRLOCK_OPEN, animated = FALSE) return FALSE SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE) - sleep(TRAM_DOOR_CRUSH_TIME) + var/opaque_delay = animation_segment_delay(AIRLOCK_CLOSING_OPAQUE) - unpassable_delay + sleep(opaque_delay) set_density(TRUE) if(!isnull(filler)) filler.set_density(TRUE) @@ -117,12 +118,34 @@ air_update_turf(TRUE, TRUE) crush() crushing_in_progress = FALSE - sleep(TRAM_DOOR_WARNING_TIME) - update_icon(ALL, AIRLOCK_CLOSED, 1) - operating = FALSE + var/close_delay = animation_segment_delay(AIRLOCK_CLOSING_FINISHED) - unpassable_delay - opaque_delay + sleep(close_delay) + set_airlock_state(AIRLOCK_CLOSED, animated = FALSE) retry_counter = 0 return TRUE +/obj/machinery/door/airlock/tram/animation_length(animation) + switch(animation) + if(DOOR_OPENING_ANIMATION) + return 1.8 SECONDS + if(DOOR_CLOSING_ANIMATION) + return 2.5 SECONDS + +/obj/machinery/door/airlock/tram/animation_segment_delay(animation) + switch(animation) + if(AIRLOCK_OPENING_TRANSPARENT) + return 0.9 SECONDS + if(AIRLOCK_OPENING_PASSABLE) + return 0.9 SECONDS + if(AIRLOCK_OPENING_FINISHED) + return 1.8 SECONDS + if(AIRLOCK_CLOSING_UNPASSABLE) + return 0.9 SECONDS + if(AIRLOCK_CLOSING_OPAQUE) + return 1.6 SECONDS + if(AIRLOCK_CLOSING_FINISHED) + return 2.5 SECONDS + /** * Crush the jerk holding up the tram from moving * @@ -235,8 +258,9 @@ return if((tram_part.travel_remaining < DEFAULT_TRAM_LENGTH || tram_part.travel_remaining > tram_part.travel_trip_length - DEFAULT_TRAM_LENGTH) && tram_part.controller_active) return // we're already animating, don't reset that - open(forced = BYPASS_DOOR_CHECKS) - return + set_airlock_state(AIRLOCK_OPENING, animated = TRUE) + if(do_after(user, delay = 0.6 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE | IGNORE_SLOWDOWNS)) + open(forced = BYPASS_DOOR_CHECKS) #undef TRAM_DOOR_WARNING_TIME #undef TRAM_DOOR_CYCLE_TIME