add the ability to build airlock chambers (#31514)

* add the ability to build airlock chambers

* fix identical variables lint

* switch to span macros

* make return values consistent

* can remove access elecs with crowbar
This commit is contained in:
warriorstar-orion
2026-02-11 22:28:54 +00:00
committed by GitHub
parent 4637126a27
commit 9238ae0f0e
12 changed files with 299 additions and 5 deletions
+1
View File
@@ -40,6 +40,7 @@
#define PIPE_TEMPERATURE_GATE 40
#define PIPE_GAS_SENSOR 98
#define PIPE_METER 99
#define PIPE_HIGH_VOLUME_PUMP 111
// MARK: Disposals pipes
#define PIPE_DISPOSALS_STRAIGHT 100
+8
View File
@@ -249,6 +249,14 @@ GLOBAL_LIST_EMPTY(rpd_pipe_list) //Some pipes we don't want to be dispensable
pipe_category = RPD_DEVICES
rpd_dispensable = TRUE
/datum/pipes/atmospheric/high_volume_vent
pipe_name = "high volume vent pump"
pipe_id = PIPE_HIGH_VOLUME_PUMP
orientations = 4
pipe_icon = "uvent"
pipe_category = RPD_DEVICES
rpd_dispensable = TRUE
//Heat exchange pipes
/datum/pipes/atmospheric/simple/he
@@ -15,12 +15,18 @@ GLOBAL_LIST_EMPTY(all_airlock_access_buttons)
/// Command, whether this button cycles in or out. This is /tmp/ so mappers dont try and define it in a map. Its assigned at runtime.
var/tmp/assigned_command
/obj/machinery/access_button/Initialize(mapload)
/obj/machinery/access_button/Initialize(mapload, direction)
. = ..()
GLOB.all_airlock_access_buttons += src
if(assigned_command)
stack_trace("A mapper tried to set assigned_command to [assigned_command] on [type] at [x],[y],[z]. This should not be mapped in.")
if(direction)
setDir(direction)
if(!mapload)
set_pixel_offsets_from_dir(25, -25, 25, -25)
/obj/machinery/access_button/Destroy()
GLOB.all_airlock_access_buttons -= src
return ..()
@@ -37,6 +37,20 @@
// Program vars
var/target_pressure
/obj/machinery/airlock_controller/Initialize(mapload, direction)
. = ..()
if(direction)
setDir(direction)
if(!mapload)
set_pixel_offsets_from_dir(25, -25, 25, -25)
vent_link_id = VENT_ID(UID())
ext_door_link_id = EXT_DOOR_ID(UID())
int_door_link_id = INT_DOOR_ID(UID())
ext_button_link_id = EXT_BTN_ID(UID())
int_button_link_id = INT_BTN_ID(UID())
/obj/machinery/airlock_controller/proc/link_all_items()
for(var/obj/machinery/door/airlock/A in GLOB.airlocks)
if(A.id_tag == int_door_link_id)
@@ -105,6 +105,17 @@
if("clear_all")
selected_accesses = list()
/obj/item/airlock_electronics/proc/apply_access_to(obj/target)
if(!istype(target))
return
if(one_access)
target.req_access = null
target.req_one_access = selected_accesses
else
target.req_one_access = null
target.req_access = selected_accesses
/obj/item/airlock_electronics/destroyed
name = "burned-out airlock electronics"
icon_state = "door_electronics_smoked"
@@ -297,6 +297,13 @@
doorOpen = 'sound/machines/airlock_ext_open.ogg'
doorClose = 'sound/machines/airlock_ext_close.ogg'
/obj/machinery/door/airlock/external/item_interaction(mob/living/user, obj/item/used, list/modifiers)
// Snowflakey but we're not a tool and don't get first mover advantage in atom interaction so shruggles
if(istype(used, /obj/item/mounted/frame/airlock_controller))
return
return ..()
/obj/machinery/door/airlock/external/glass
opacity = FALSE
glass = TRUE
@@ -40,6 +40,7 @@ GLOBAL_LIST_INIT(pipe_path2type, list(
/obj/machinery/atmospherics/unary/outlet_injector = PIPE_INJECTOR,
/obj/machinery/atmospherics/unary/passive_vent = PIPE_PASV_VENT,
/obj/machinery/atmospherics/binary/circulator = PIPE_CIRCULATOR,
/obj/machinery/atmospherics/unary/vent_pump/high_volume = PIPE_HIGH_VOLUME_PUMP,
"[PIPE_JUNCTION]" = /obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction,
"[PIPE_HE_STRAIGHT]" = /obj/machinery/atmospherics/pipe/simple/heat_exchanging,
"[PIPE_HE_BENT]" = /obj/machinery/atmospherics/pipe/simple/heat_exchanging,
@@ -76,6 +77,7 @@ GLOBAL_LIST_INIT(pipe_path2type, list(
"[PIPE_INJECTOR]" = /obj/machinery/atmospherics/unary/outlet_injector,
"[PIPE_PASV_VENT]" = /obj/machinery/atmospherics/unary/passive_vent,
"[PIPE_CIRCULATOR]" = /obj/machinery/atmospherics/binary/circulator,
"[PIPE_HIGH_VOLUME_PUMP]" = /obj/machinery/atmospherics/unary/vent_pump/high_volume,
))
/obj/item/pipe
@@ -255,7 +257,7 @@ GLOBAL_LIST_INIT(pipe_path2type, list(
return flip
if(PIPE_SIMPLE_BENT, PIPE_HE_BENT, PIPE_SUPPLY_BENT, PIPE_SCRUBBERS_BENT,
PIPE_UVENT, PIPE_PASV_VENT, PIPE_SCRUBBER, PIPE_INJECTOR)
PIPE_UVENT, PIPE_PASV_VENT, PIPE_SCRUBBER, PIPE_INJECTOR, PIPE_HIGH_VOLUME_PUMP)
return dir
if(PIPE_SIMPLE_STRAIGHT, PIPE_HE_STRAIGHT, PIPE_JUNCTION,
@@ -0,0 +1,242 @@
#define LINK_STAGE_NOT_STARTED 0
#define LINK_STAGE_EXTERIOR_BUTTONS 1
#define LINK_STAGE_EXTERIOR_AIRLOCKS 2
#define LINK_STAGE_INTERIOR_BUTTONS 3
#define LINK_STAGE_INTERIOR_AIRLOCKS 4
#define LINK_STAGE_VENTS 5
#define LINK_STAGE_COMPLETE 6
/obj/item/mounted/frame/airlock_button
name = "airlock button frame"
desc = "Used for building airlock buttons."
icon = 'icons/obj/airlock_machines.dmi'
icon_state = "access_button_off"
// external airlock buttons are expected to be placed in space
mount_requirements = MOUNTED_FRAME_SIMFLOOR
/obj/item/mounted/frame/airlock_button/do_build(turf/on_wall, mob/user)
new /obj/machinery/access_button(get_turf(src), get_dir(user, on_wall))
qdel(src)
/obj/item/mounted/frame/airlock_controller
name = "airlock controller frame"
desc = ABSTRACT_TYPE_DESC
icon = 'icons/obj/airlock_machines.dmi'
icon_state = "access_control_off"
mount_requirements = MOUNTED_FRAME_SIMFLOOR | MOUNTED_FRAME_NOSPACE
var/obj/item/airlock_electronics/access_electronics
var/link_stage = LINK_STAGE_NOT_STARTED
var/result_controller_type
var/list/interior_airlocks = list()
var/list/interior_buttons = list()
var/list/exterior_airlocks = list()
var/list/exterior_buttons = list()
/obj/item/mounted/frame/airlock_controller/Destroy()
. = ..()
QDEL_NULL(access_electronics)
interior_airlocks.Cut()
interior_buttons.Cut()
exterior_airlocks.Cut()
exterior_buttons.Cut()
/obj/item/mounted/frame/airlock_controller/examine(mob/user)
. = ..()
if(istype(access_electronics))
. += SPAN_NOTICE("Airlock electronics have been inserted to configure access for all its airlocks and buttons.")
else
. += SPAN_NOTICE("Airlock electronics may be placed in it to configure access for all its airlocks and buttons.")
switch(link_stage)
if(LINK_STAGE_NOT_STARTED)
. += SPAN_NOTICE("It requires its exterior buttons linked next.")
if(LINK_STAGE_EXTERIOR_BUTTONS)
. += SPAN_NOTICE("It requires its exterior airlocks linked next.")
if(LINK_STAGE_EXTERIOR_AIRLOCKS)
. += SPAN_NOTICE("It requires its interior buttons linked next.")
if(LINK_STAGE_INTERIOR_BUTTONS)
. += SPAN_NOTICE("It requires its interior airlocks linked next.")
/obj/item/mounted/frame/airlock_controller/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(..())
return ITEM_INTERACT_COMPLETE
return attempt_item_link(user, target)
/obj/item/mounted/frame/airlock_controller/try_build(turf/on_wall, mob/user)
. = ..()
var/list/missing_items = get_missing_items()
if(length(missing_items))
to_chat(user, SPAN_NOTICE("[src] cannot be placed yet. It is missing at least one [english_list(missing_items)]."))
return FALSE
return TRUE
/obj/item/mounted/frame/airlock_controller/item_interaction(mob/living/user, obj/item/used, list/modifiers)
var/obj/item/airlock_electronics/airlock_electronics = used
if(istype(airlock_electronics))
if(istype(access_electronics))
to_chat(user, SPAN_NOTICE("There is already \a [access_electronics] in [src]."))
else
if(user.unequip(used))
access_electronics = airlock_electronics
used.forceMove(src)
else
to_chat(user, SPAN_NOTICE("You can't put [airlock_electronics] in [src]."))
return ..()
/obj/item/mounted/frame/airlock_controller/do_build(turf/on_wall, mob/user)
var/obj/machinery/airlock_controller/controller = new result_controller_type(get_turf(src), get_dir(user, on_wall))
setup_controller(controller)
controller.link_all_items()
qdel(src)
/obj/item/mounted/frame/airlock_controller/crowbar_act(mob/living/user, obj/item/I)
if(access_electronics)
user.put_in_hands(access_electronics)
to_chat(user, SPAN_NOTICE("You remove [access_electronics] from [src] with [I]."))
access_electronics = null
/obj/item/mounted/frame/airlock_controller/proc/get_missing_items()
. = list()
if(!length(exterior_buttons))
. += "exterior button"
if(!length(exterior_airlocks))
. += "exterior airlock"
if(!length(interior_buttons))
. += "interior button"
if(!length(interior_airlocks))
. += "interior airlock"
/obj/item/mounted/frame/airlock_controller/proc/attempt_item_link(mob/user, atom/target)
SHOULD_CALL_PARENT(TRUE)
var/obj/machinery/door/airlock/external/airlock = target
var/obj/machinery/access_button/button = target
if(!istype(airlock) && !istype(button))
return
switch(link_stage)
if(LINK_STAGE_COMPLETE)
to_chat(user, SPAN_NOTICE("[src] has already been linked to its components."))
return ITEM_INTERACT_COMPLETE
if(LINK_STAGE_NOT_STARTED)
if(!istype(button))
to_chat(user, SPAN_NOTICE("[src] must have its exterior buttons linked first."))
return ITEM_INTERACT_COMPLETE
link_stage = LINK_STAGE_EXTERIOR_BUTTONS
return attempt_item_link(user, target)
if(LINK_STAGE_EXTERIOR_BUTTONS)
if(istype(button))
to_chat(user, SPAN_NOTICE("You link [button] to [src]."))
exterior_buttons |= button
return ITEM_INTERACT_COMPLETE
if(istype(airlock))
link_stage = LINK_STAGE_EXTERIOR_AIRLOCKS
return attempt_item_link(user, target)
if(LINK_STAGE_EXTERIOR_AIRLOCKS)
if(istype(airlock))
to_chat(user, SPAN_NOTICE("You link [airlock] to [src]."))
exterior_airlocks |= airlock
return ITEM_INTERACT_COMPLETE
if(istype(button))
link_stage = LINK_STAGE_INTERIOR_BUTTONS
return attempt_item_link(user, target)
if(LINK_STAGE_INTERIOR_BUTTONS)
if(istype(button))
to_chat(user, SPAN_NOTICE("You link [button] to [src]."))
interior_buttons |= airlock
return ITEM_INTERACT_COMPLETE
if(istype(airlock))
link_stage = LINK_STAGE_INTERIOR_AIRLOCKS
return attempt_item_link(user, target)
if(LINK_STAGE_INTERIOR_AIRLOCKS)
if(istype(airlock))
to_chat(user, SPAN_NOTICE("You link [airlock] to [src]."))
interior_airlocks |= airlock
return ITEM_INTERACT_COMPLETE
/obj/item/mounted/frame/airlock_controller/proc/setup_controller(obj/machinery/airlock_controller/controller)
for(var/obj/machinery/door/airlock/airlock in interior_airlocks)
airlock.id_tag = controller.int_door_link_id
if(access_electronics)
access_electronics.apply_access_to(airlock)
for(var/obj/machinery/access_button/button in interior_buttons)
button.autolink_id = controller.int_button_link_id
if(access_electronics)
access_electronics.apply_access_to(button)
for(var/obj/machinery/door/airlock/airlock in exterior_airlocks)
airlock.id_tag = controller.ext_door_link_id
if(access_electronics)
access_electronics.apply_access_to(airlock)
for(var/obj/machinery/access_button/button in exterior_buttons)
button.autolink_id = controller.ext_button_link_id
if(access_electronics)
access_electronics.apply_access_to(button)
/obj/item/mounted/frame/airlock_controller/access_controller
name = "airlock access controller frame"
desc = "Used for building airlock chambers."
result_controller_type = /obj/machinery/airlock_controller/access_controller
/obj/item/mounted/frame/airlock_controller/air_cycler
name = "cycling airlock controller frame"
desc = "Used for building cycling airlocks."
icon_state = "airlock_control_off"
result_controller_type = /obj/machinery/airlock_controller/air_cycler
var/list/vents = list()
/obj/item/mounted/frame/airlock_controller/air_cycler/Destroy()
. = ..()
vents.Cut()
/obj/item/mounted/frame/airlock_controller/air_cycler/examine(mob/user)
. = ..()
switch(link_stage)
if(LINK_STAGE_INTERIOR_AIRLOCKS)
. += SPAN_NOTICE("It requires its vents linked next.")
/obj/item/mounted/frame/airlock_controller/air_cycler/setup_controller(obj/machinery/airlock_controller/air_cycler/controller)
. = ..()
for(var/obj/machinery/atmospherics/unary/vent_pump/vent in vents)
vent.autolink_id = controller.vent_link_id
/obj/item/mounted/frame/airlock_controller/air_cycler/attempt_item_link(mob/user, atom/target)
. = ..()
if(.)
return
var/obj/machinery/atmospherics/unary/vent_pump/vent = target
switch(link_stage)
if(LINK_STAGE_INTERIOR_AIRLOCKS)
if(istype(vent))
link_stage = LINK_STAGE_VENTS
return attempt_item_link(user, target)
if(LINK_STAGE_VENTS)
if(istype(vent))
to_chat(user, SPAN_NOTICE("You link [vent] to [src]."))
vents |= vent
return ITEM_INTERACT_COMPLETE
/obj/item/mounted/frame/airlock_controller/air_cycler/get_missing_items()
. = ..()
if(!length(vents))
. += "vent"
#undef LINK_STAGE_NOT_STARTED
#undef LINK_STAGE_EXTERIOR_BUTTONS
#undef LINK_STAGE_EXTERIOR_AIRLOCKS
#undef LINK_STAGE_INTERIOR_BUTTONS
#undef LINK_STAGE_INTERIOR_AIRLOCKS
#undef LINK_STAGE_VENTS
#undef LINK_STAGE_COMPLETE
@@ -11,7 +11,7 @@
if(is_type_in_list(target, buildon_types))
if(try_build(target, user))
do_build(target, user)
return ITEM_INTERACT_COMPLETE
return ITEM_INTERACT_COMPLETE
..()
/obj/item/mounted/proc/try_build(turf/on_wall, mob/user) //checks
+1 -1
View File
@@ -115,7 +115,7 @@
P = new(T, whatpipe, iconrotation) //Make the pipe, BUT WAIT! There's more!
if(!iconrotation && P.is_bent_pipe()) //Automatically rotates dispensed pipes if the user selected auto-rotation
P.dir = turn(user.dir, 135)
else if(!iconrotation && (P.pipe_type in list(PIPE_CONNECTOR, PIPE_UVENT, PIPE_SCRUBBER, PIPE_HEAT_EXCHANGE, PIPE_CAP, PIPE_SUPPLY_CAP, PIPE_SCRUBBERS_CAP, PIPE_INJECTOR, PIPE_PASV_VENT))) //Some pipes dispense oppositely to what you'd expect, but we don't want to do anything if they selected a direction
else if(!iconrotation && (P.pipe_type in list(PIPE_CONNECTOR, PIPE_UVENT, PIPE_SCRUBBER, PIPE_HEAT_EXCHANGE, PIPE_CAP, PIPE_SUPPLY_CAP, PIPE_SCRUBBERS_CAP, PIPE_INJECTOR, PIPE_PASV_VENT, PIPE_HIGH_VOLUME_PUMP))) //Some pipes dispense oppositely to what you'd expect, but we don't want to do anything if they selected a direction
P.dir = turn(user.dir, -180)
else if(iconrotation && P.is_bent_pipe()) //If user selected a rotation and the pipe is bent
P.dir = turn(iconrotation, -45)
@@ -75,7 +75,9 @@ GLOBAL_LIST_INIT(metal_recipes, list(
new /datum/stack_recipe("extinguisher cabinet frame", /obj/item/mounted/frame/extinguisher, 2),
new /datum/stack_recipe/barsign_frame("bar sign frame", /obj/machinery/barsign, 4),
new /datum/stack_recipe("mass driver button frame", /obj/item/mounted/frame/driver_button, 1, time = 5 SECONDS, one_per_turf = FALSE, on_floor = TRUE),
new /datum/stack_recipe("airlock access button frame", /obj/item/mounted/frame/airlock_button, 2),
new /datum/stack_recipe("airlock controller frame", /obj/item/mounted/frame/airlock_controller/access_controller, 2),
new /datum/stack_recipe("cycling airlock controller frame", /obj/item/mounted/frame/airlock_controller/air_cycler, 2),
)),
new /datum/stack_recipe_list("construction", list(
new /datum/stack_recipe("wall girders", /obj/structure/girder, 2, time = 5 SECONDS, one_per_turf = TRUE, on_floor = TRUE),
+1
View File
@@ -1400,6 +1400,7 @@
#include "code\game\objects\items\granters\crafting_granters\_crafting_granter.dm"
#include "code\game\objects\items\granters\crafting_granters\combat_baking.dm"
#include "code\game\objects\items\mountable_frames\air_alarm_frame.dm"
#include "code\game\objects\items\mountable_frames\airlock_chamber_frames.dm"
#include "code\game\objects\items\mountable_frames\apc_frame.dm"
#include "code\game\objects\items\mountable_frames\buttons_switches.dm"
#include "code\game\objects\items\mountable_frames\extinguisher_frame.dm"