Added passive vents (#21796)

Passive vents, as the name suggests, passively interact with the
atmosphere. Basically think of them as an open pipe.

This makes them useful to vent a pipe network to a vacuum, or equalize
the atmospheres of two otherwise unconnected rooms. Being passive
however, they are just as capable of intaking air as they are outputting
air, with no way to control it. If you want to guarantee that air only
goes -out-, use an injector or a pump vent.
This commit is contained in:
Cody Brittain
2026-02-08 22:03:47 +00:00
committed by GitHub
parent 533ae66fc9
commit 93ea0a977a
10 changed files with 273 additions and 14 deletions
+1
View File
@@ -1731,6 +1731,7 @@
#include "code\modules\atmospherics\datum_pipeline.dm"
#include "code\modules\atmospherics\he_pipes.dm"
#include "code\modules\atmospherics\pipes.dm"
#include "code\modules\atmospherics\vent_passive.dm"
#include "code\modules\atmospherics\components\portables_connector.dm"
#include "code\modules\atmospherics\components\tvalve.dm"
#include "code\modules\atmospherics\components\valve.dm"
+2
View File
@@ -74,3 +74,5 @@
#define PIPE_CONNECTOR_AUX 63
#define PIPE_AUX_UVENT 64
#define PIPE_PVENT 65
+21 -3
View File
@@ -168,6 +168,8 @@
src.pipe_type = PIPE_OMNI_MIXER
else if(istype(make_from, /obj/machinery/atmospherics/omni/filter))
src.pipe_type = PIPE_OMNI_FILTER
else if(istype(make_from, /obj/machinery/atmospherics/pipe/vent_passive))
src.pipe_type = PIPE_PVENT
///// Z-Level stuff
else if(istype(make_from, /obj/machinery/atmospherics/pipe/zpipe/up/supply))
src.pipe_type = PIPE_SUPPLY_UP
@@ -306,7 +308,8 @@
"auxiliary gas pump",
"fuel connector",
"auxiliary connector",
"auxiliary unary vent"
"auxiliary unary vent",
"passive vent"
)
name = nlist[pipe_type+1] + " fitting"
var/list/islist = list(
@@ -378,7 +381,8 @@
"pump",
"connector",
"connector",
"uvent"
"uvent",
"pvent"
)
icon_state = islist[pipe_type + 1]
@@ -449,7 +453,7 @@
return dir|flip
if(PIPE_SIMPLE_BENT, PIPE_HE_BENT, PIPE_SUPPLY_BENT, PIPE_SCRUBBERS_BENT, PIPE_FUEL_BENT, PIPE_AUX_BENT)
return dir //dir|acw
if(PIPE_CONNECTOR,PIPE_CONNECTOR_FUEL,PIPE_CONNECTOR_AUX,PIPE_UVENT,PIPE_AUX_UVENT,PIPE_SCRUBBER,PIPE_HEAT_EXCHANGE)
if(PIPE_CONNECTOR,PIPE_CONNECTOR_FUEL,PIPE_CONNECTOR_AUX,PIPE_UVENT,PIPE_AUX_UVENT,PIPE_SCRUBBER,PIPE_HEAT_EXCHANGE,PIPE_PVENT)
return dir
if(PIPE_MANIFOLD4W, PIPE_SUPPLY_MANIFOLD4W, PIPE_SCRUBBERS_MANIFOLD4W, PIPE_FUEL_MANIFOLD4W, PIPE_AUX_MANIFOLD4W, PIPE_OMNI_MIXER, PIPE_OMNI_FILTER)
return dir|flip|cw|acw
@@ -1075,6 +1079,20 @@
V.node2.atmos_init()
V.node2.build_network()
if(PIPE_PVENT) //passive vent
var/obj/machinery/atmospherics/pipe/vent_passive/V = new (src.loc)
V.set_dir(dir)
V.initialize_directions = pipe_dir
if(pipename)
V.name = pipename
var/turf/T = V.loc
V.level = !T.is_plating() ? 2 : 1
V.build_network()
V.atmos_init()
if (V.node1)
V.node1.atmos_init()
V.node1.build_network()
if(PIPE_PUMP) //gas pump
var/obj/machinery/atmospherics/binary/pump/P = new(src.loc)
P.set_dir(dir)
@@ -70,6 +70,7 @@
<A href='byond://?src=[REF(src)];make=63;dir=1'>Auxiliary Connector</A><BR>
<A href='byond://?src=[REF(src)];make=7;dir=1'>Unary Vent</A><BR>
<A href='byond://?src=[REF(src)];make=64;dir=1'>Auxiliary Unary Vent</A><BR>
<A href='byond://?src=[REF(src)];make=65;dir=1'>Passive Vent</A><BR>
<A href='byond://?src=[REF(src)];make=9;dir=1'>Gas Pump</A><BR>
<A href='byond://?src=[REF(src)];make=60;dir=1'>Fuel Gas Pump</A><BR>
<A href='byond://?src=[REF(src)];make=61;dir=1'>Auxiliary Gas Pump</A><BR>
@@ -92,15 +92,11 @@
return null
/obj/machinery/atmospherics/unary/proc/is_welded()
/obj/machinery/atmospherics/proc/is_welded()
return FALSE
/obj/machinery/atmospherics/unary/vent_pump/is_welded()
if (welded > 0)
return TRUE
return FALSE
return welded
/obj/machinery/atmospherics/unary/vent_scrubber/is_welded()
if (welded > 0)
return TRUE
return FALSE
return welded
+8 -4
View File
@@ -115,24 +115,28 @@
return network
/datum/pipeline/proc/mingle_with_turf(turf/simulated/target, mingle_volume)
if(!isturf(target))
return
var/datum/gas_mixture/air_sample = air.remove_ratio(mingle_volume/air.volume)
air_sample.volume = mingle_volume
if(istype(target) && target.zone)
if(target.zone)
//Have to consider preservation of group statuses
var/datum/gas_mixture/turf_copy = new
var/datum/gas_mixture/turf_original = new
turf_copy.copy_from(target.zone.air)
turf_copy.volume = target.zone.air.volume //Copy a good representation of the turf from parent group
turf_original.copy_from(turf_copy)
equalize_gases(list(air_sample, turf_copy))
air.merge(air_sample)
turf_copy.subtract(target.zone.air)
target.zone.air.remove(turf_original.total_moles)
target.zone.air.merge(turf_copy)
else if(target)
else
var/datum/gas_mixture/turf_air = target.return_air()
equalize_gases(list(air_sample, turf_air))
+178
View File
@@ -0,0 +1,178 @@
/obj/machinery/atmospherics/pipe/vent_passive
name = "passive vent"
desc = ""
icon = 'icons/atmos/vent_passive.dmi'
icon_state = "map_vent"
connect_types = CONNECT_TYPE_REGULAR|CONNECT_TYPE_SUPPLY|CONNECT_TYPE_SCRUBBER|CONNECT_TYPE_AUX|CONNECT_TYPE_FUEL //connects to all pipes except HE
level = 1
volume = 250
dir = SOUTH
initialize_directions = SOUTH
var/obj/machinery/atmospherics/node
var/welded = FALSE
/obj/machinery/atmospherics/pipe/vent_passive/Initialize()
initialize_directions = dir
. = ..()
/obj/machinery/atmospherics/pipe/vent_passive/hide(var/i)
if(istype(loc, /turf/simulated))
set_invisibility(i ? 101 : 0)
queue_icon_update()
/obj/machinery/atmospherics/pipe/vent_passive/mechanics_hints(mob/user, distance, is_adjacent)
. = ..()
. += "This passively outputs the contents of the attached pipe out into the atmosphere."
/obj/machinery/atmospherics/pipe/vent_passive/feedback_hints(mob/user, distance, is_adjacent)
. = list()
if(welded)
. += "It seems welded shut."
/obj/machinery/atmospherics/pipe/vent_passive/update_icon(safety = 0)
var/vent_icon = ""
if(welded)
vent_icon += "weld"
else
vent_icon += "vent"
icon_state = vent_icon
update_underlays()
/obj/machinery/atmospherics/pipe/vent_passive/update_underlays()
if(..())
underlays.Cut()
var/turf/T = get_turf(src)
if(!istype(T))
return
if(!T.is_plating() && node && node.level == 1 && istype(node, /obj/machinery/atmospherics/pipe))
return
else
if(node)
add_underlay(T, node, dir, node.icon_connect_type)
else
add_underlay(T,, dir)
underlays += "frame"
/obj/machinery/atmospherics/pipe/vent_passive/process(seconds_per_tick)
if(!parent)
..()
else
parent.mingle_with_turf(loc, volume)
/obj/machinery/atmospherics/pipe/vent_passive/Destroy()
if(node)
node.disconnect(src)
node = null
return ..()
/obj/machinery/atmospherics/pipe/vent_passive/pipeline_expansion()
return list(node)
/obj/machinery/atmospherics/pipe/vent_passive/atmos_init()
for(var/obj/machinery/atmospherics/target in get_step(src, dir))
if(target.initialize_directions & get_dir(target,src))
if (check_connect_types(target,src))
node = target
break
atmos_initialised = TRUE
SSicon_update.add_to_queue(src)
/obj/machinery/atmospherics/pipe/vent_passive/disconnect(obj/machinery/atmospherics/reference)
if(reference == node)
if(istype(node, /obj/machinery/atmospherics/pipe))
qdel(parent)
node = null
update_icon()
return null
/obj/machinery/atmospherics/pipe/vent_passive/attackby(obj/item/attacking_item, mob/user)
if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/WT = attacking_item
if (!WT.welding)
to_chat(user, SPAN_DANGER("\The [WT] must be turned on!"))
else if (WT.use(0,user))
to_chat(user, SPAN_NOTICE("Now welding the vent."))
if(attacking_item.use_tool(src, user, 30, volume = 50))
if(!src || !WT.isOn())
return TRUE
welded = !welded
update_icon()
playsound(src, 'sound/items/welder_pry.ogg', 50, 1)
user.visible_message(SPAN_NOTICE("\The [user] [welded ? "welds \the [src] shut" : "unwelds \the [src]"]."), \
SPAN_NOTICE("You [welded ? "weld \the [src] shut" : "unweld \the [src]"]."), \
"You hear welding.")
else
to_chat(user, SPAN_NOTICE("You fail to complete the welding."))
else
to_chat(user, SPAN_WARNING("You need more welding fuel to complete this task."))
return TRUE
else if(istype(attacking_item, /obj/item/melee/arm_blade))
if(!welded)
to_chat(user, SPAN_WARNING("\The [attacking_item] can only be used to tear open welded air vents!"))
return TRUE
user.visible_message(SPAN_WARNING("\The [user] starts using \the [attacking_item] to hack open \the [src]!"), SPAN_NOTICE("You start hacking open \the [src] with \the [attacking_item]..."))
user.do_attack_animation(src, attacking_item)
playsound(loc, 'sound/weapons/smash.ogg', 60, TRUE)
var/cut_amount = 3
for(var/i = 0; i <= cut_amount; i++)
if(!attacking_item || !do_after(user, 30, src))
return TRUE
user.do_attack_animation(src, attacking_item)
user.visible_message(SPAN_WARNING("\The [user] smashes \the [attacking_item] into \the [src]!"), SPAN_NOTICE("You smash \the [attacking_item] into \the [src]."))
playsound(loc, 'sound/weapons/smash.ogg', 60, TRUE)
if(i == cut_amount)
welded = FALSE
spark(get_turf(src), 3, GLOB.alldirs)
playsound(loc, 'sound/items/welder_pry.ogg', 50, TRUE)
update_icon()
else if(attacking_item.tool_behaviour == TOOL_WRENCH)
var/turf/T = src.loc
if(level==1 && isturf(T) && !T.is_plating())
to_chat(user, SPAN_WARNING("You must remove the plating first."))
else if(loc)
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
if((int_air.return_pressure()-env_air.return_pressure()) > PRESSURE_EXERTED)
to_chat(user, SPAN_WARNING("You cannot unwrench \the [src], it is too exerted due to internal pressure."))
add_fingerprint(user)
else
to_chat(user, SPAN_NOTICE("You begin to unfasten \the [src]..."))
if(attacking_item.use_tool(src, user, istype(attacking_item, /obj/item/pipewrench) ? 80 : 40, volume = 50))
user.visible_message(SPAN_NOTICE("\The [user] unfastens \the [src]."), \
SPAN_NOTICE("You have unfastened \the [src]."), \
"You hear a ratchet.")
new /obj/item/pipe(loc, make_from=src)
qdel(src)
else if(istype(attacking_item, /obj/item/analyzer) && in_range(user, src))
var/obj/item/analyzer/A = attacking_item
A.analyze_gases(src, user)
return TRUE
return ..()
/obj/machinery/atmospherics/pipe/vent_passive/is_welded()
return welded
@@ -0,0 +1,59 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: GeneralCamo
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- rscadd: "Added passive vents, that passively interact with the air outside of them."
- bugfix: "Vent and scrubber items will now use the correct icons."
Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB