diff --git a/code/ATMOSPHERICS/components/shutoff.dm b/code/ATMOSPHERICS/components/shutoff.dm
new file mode 100644
index 0000000000..b1fb43baa1
--- /dev/null
+++ b/code/ATMOSPHERICS/components/shutoff.dm
@@ -0,0 +1,50 @@
+/obj/machinery/atmospherics/valve/shutoff
+ icon = 'icons/atmos/clamp.dmi'
+ icon_state = "map_vclamp0"
+
+ name = "automatic shutoff valve"
+ desc = "An automatic valve with control circuitry and pipe integrity sensor, capable of automatically isolating damaged segments of the pipe network."
+ var/close_on_leaks = TRUE // If false it will be always open
+ level = 1
+ connect_types = CONNECT_TYPE_SCRUBBER | CONNECT_TYPE_SUPPLY | CONNECT_TYPE_REGULAR
+
+/obj/machinery/atmospherics/valve/shutoff/update_icon()
+ icon_state = "vclamp[open]"
+
+/obj/machinery/atmospherics/valve/shutoff/examine(var/mob/user)
+ ..()
+ to_chat(user, "The automatic shutoff circuit is [close_on_leaks ? "enabled" : "disabled"].")
+
+/obj/machinery/atmospherics/valve/shutoff/Initialize()
+ . = ..()
+ open()
+ hide(1)
+
+/obj/machinery/atmospherics/valve/shutoff/attack_ai(mob/user as mob)
+ return src.attack_hand(user)
+
+/obj/machinery/atmospherics/valve/shutoff/attack_hand(var/mob/user)
+ src.add_fingerprint(usr)
+ update_icon(1)
+ close_on_leaks = !close_on_leaks
+ to_chat(user, "You [close_on_leaks ? "enable" : "disable"] the automatic shutoff circuit.")
+ return TRUE
+
+/obj/machinery/atmospherics/valve/shutoff/process()
+ ..()
+
+ if (!network_node1 || !network_node2)
+ if(open)
+ close()
+ return
+
+ if (!close_on_leaks)
+ if (!open)
+ open()
+ return
+
+ if (network_node1.leaks.len || network_node2.leaks.len)
+ if (open)
+ close()
+ else if (!open)
+ open()
diff --git a/code/ATMOSPHERICS/datum_pipe_network.dm b/code/ATMOSPHERICS/datum_pipe_network.dm
index eb5e89276e..a611e30a89 100644
--- a/code/ATMOSPHERICS/datum_pipe_network.dm
+++ b/code/ATMOSPHERICS/datum_pipe_network.dm
@@ -8,76 +8,83 @@ var/global/list/datum/pipe_network/pipe_networks = list() // TODO - Move into SS
var/list/datum/pipeline/line_members = list()
//membership roster to go through for updates and what not
+ var/list/leaks = list()
+
var/update = 1
//var/datum/gas_mixture/air_transient = null
- Destroy()
- STOP_PROCESSING_PIPENET(src)
- for(var/datum/pipeline/line_member in line_members)
- line_member.network = null
- for(var/obj/machinery/atmospherics/normal_member in normal_members)
- normal_member.reassign_network(src, null)
- gases.Cut() // Do not qdel the gases, we don't own them
- return ..()
+/datum/pipe_network/Destroy()
+ STOP_PROCESSING_PIPENET(src)
+ for(var/datum/pipeline/line_member in line_members)
+ line_member.network = null
+ for(var/obj/machinery/atmospherics/normal_member in normal_members)
+ normal_member.reassign_network(src, null)
+ gases.Cut() // Do not qdel the gases, we don't own them
+ leaks.Cut()
+ return ..()
- process()
- //Equalize gases amongst pipe if called for
- if(update)
- update = 0
- reconcile_air() //equalize_gases(gases)
+/datum/pipe_network/process()
+ //Equalize gases amongst pipe if called for
+ if(update)
+ update = 0
+ reconcile_air() //equalize_gases(gases)
- //Give pipelines their process call for pressure checking and what not. Have to remove pressure checks for the time being as pipes dont radiate heat - Mport
- //for(var/datum/pipeline/line_member in line_members)
- // line_member.process()
+ listclearnulls(leaks) // Let's not have forever-seals.
- proc/build_network(obj/machinery/atmospherics/start_normal, obj/machinery/atmospherics/reference)
- //Purpose: Generate membership roster
- //Notes: Assuming that members will add themselves to appropriate roster in network_expand()
+ //Give pipelines their process call for pressure checking and what not. Have to remove pressure checks for the time being as pipes dont radiate heat - Mport
+ //for(var/datum/pipeline/line_member in line_members)
+ // line_member.process()
- if(!start_normal)
- qdel(src)
- return
+/datum/pipe_network/proc/build_network(obj/machinery/atmospherics/start_normal, obj/machinery/atmospherics/reference)
+ //Purpose: Generate membership roster
+ //Notes: Assuming that members will add themselves to appropriate roster in network_expand()
- start_normal.network_expand(src, reference)
+ if(!start_normal)
+ qdel(src)
+ return
- update_network_gases()
+ start_normal.network_expand(src, reference)
- if((normal_members.len>0)||(line_members.len>0))
- START_PROCESSING_PIPENET(src)
- else
- qdel(src)
+ update_network_gases()
- proc/merge(datum/pipe_network/giver)
- if(giver==src) return 0
+ if((normal_members.len>0)||(line_members.len>0))
+ START_PROCESSING_PIPENET(src)
+ else
+ qdel(src)
- normal_members |= giver.normal_members
+/datum/pipe_network/proc/merge(datum/pipe_network/giver)
+ if(giver==src) return 0
- line_members |= giver.line_members
+ normal_members |= giver.normal_members
- for(var/obj/machinery/atmospherics/normal_member in giver.normal_members)
- normal_member.reassign_network(giver, src)
+ line_members |= giver.line_members
- for(var/datum/pipeline/line_member in giver.line_members)
- line_member.network = src
+ leaks |= giver.leaks
- update_network_gases()
- return 1
+ for(var/obj/machinery/atmospherics/normal_member in giver.normal_members)
+ normal_member.reassign_network(giver, src)
- proc/update_network_gases()
- //Go through membership roster and make sure gases is up to date
+ for(var/datum/pipeline/line_member in giver.line_members)
+ line_member.network = src
- gases = list()
- volume = 0
+ update_network_gases()
+ return 1
- for(var/obj/machinery/atmospherics/normal_member in normal_members)
- var/result = normal_member.return_network_air(src)
- if(result) gases += result
+/datum/pipe_network/proc/update_network_gases()
+ //Go through membership roster and make sure gases is up to date
- for(var/datum/pipeline/line_member in line_members)
- gases += line_member.air
+ gases = list()
+ volume = 0
- for(var/datum/gas_mixture/air in gases)
- volume += air.volume
+ for(var/obj/machinery/atmospherics/normal_member in normal_members)
+ var/result = normal_member.return_network_air(src)
+ if(result) gases += result
- proc/reconcile_air()
- equalize_gases(gases)
+ for(var/datum/pipeline/line_member in line_members)
+ gases += line_member.air
+
+ for(var/datum/gas_mixture/air in gases)
+ volume += air.volume
+
+/datum/pipe_network/proc/reconcile_air()
+ equalize_gases(gases)
diff --git a/code/ATMOSPHERICS/datum_pipeline.dm b/code/ATMOSPHERICS/datum_pipeline.dm
index fc47bca938..b53d722458 100644
--- a/code/ATMOSPHERICS/datum_pipeline.dm
+++ b/code/ATMOSPHERICS/datum_pipeline.dm
@@ -1,219 +1,234 @@
-datum/pipeline
+/datum/pipeline
var/datum/gas_mixture/air
var/list/obj/machinery/atmospherics/pipe/members
var/list/obj/machinery/atmospherics/pipe/edges //Used for building networks
+ // Nodes that are leaking. Used for A.S. Valves.
+ var/list/leaks = list()
+
var/datum/pipe_network/network
var/alert_pressure = 0
- Destroy()
- QDEL_NULL(network)
+/datum/pipeline/Destroy()
+ QDEL_NULL(network)
- if(air && air.volume)
- temporarily_store_air()
- for(var/obj/machinery/atmospherics/pipe/P in members)
- P.parent = null
- members = null
- edges = null
- . = ..()
+ if(air && air.volume)
+ temporarily_store_air()
+ for(var/obj/machinery/atmospherics/pipe/P in members)
+ P.parent = null
+ members = null
+ edges = null
+ leaks = null
+ . = ..()
- process()//This use to be called called from the pipe networks
-
- //Check to see if pressure is within acceptable limits
- var/pressure = air.return_pressure()
- if(pressure > alert_pressure)
- for(var/obj/machinery/atmospherics/pipe/member in members)
- if(!member.check_pressure(pressure))
- break //Only delete 1 pipe per process
-
- proc/temporarily_store_air()
- //Update individual gas_mixtures by volume ratio
+/datum/pipeline/process()//This use to be called called from the pipe networks
+ //Check to see if pressure is within acceptable limits
+ var/pressure = air.return_pressure()
+ if(pressure > alert_pressure)
for(var/obj/machinery/atmospherics/pipe/member in members)
- member.air_temporary = new
- member.air_temporary.copy_from(air)
- member.air_temporary.volume = member.volume
- member.air_temporary.multiply(member.volume / air.volume)
+ if(!member.check_pressure(pressure))
+ break //Only delete 1 pipe per process
- proc/build_pipeline(obj/machinery/atmospherics/pipe/base)
+/datum/pipeline/proc/temporarily_store_air()
+ //Update individual gas_mixtures by volume ratio
+
+ for(var/obj/machinery/atmospherics/pipe/member in members)
+ member.air_temporary = new
+ member.air_temporary.copy_from(air)
+ member.air_temporary.volume = member.volume
+ member.air_temporary.multiply(member.volume / air.volume)
+
+/datum/pipeline/proc/build_pipeline(obj/machinery/atmospherics/pipe/base)
+ air = new
+
+ var/list/possible_expansions = list(base)
+ members = list(base)
+ edges = list()
+
+ var/volume = base.volume
+ base.parent = src
+ alert_pressure = base.alert_pressure
+
+ if(base.air_temporary)
+ air = base.air_temporary
+ base.air_temporary = null
+ else
air = new
- var/list/possible_expansions = list(base)
- members = list(base)
- edges = list()
+ if(base.leaking)
+ leaks |= base
- var/volume = base.volume
- base.parent = src
- alert_pressure = base.alert_pressure
+ while(possible_expansions.len>0)
+ for(var/obj/machinery/atmospherics/pipe/borderline in possible_expansions)
- if(base.air_temporary)
- air = base.air_temporary
- base.air_temporary = null
- else
- air = new
+ var/list/result = borderline.pipeline_expansion()
+ var/edge_check = result.len
- while(possible_expansions.len>0)
- for(var/obj/machinery/atmospherics/pipe/borderline in possible_expansions)
+ if(result.len>0)
+ for(var/obj/machinery/atmospherics/pipe/item in result)
- var/list/result = borderline.pipeline_expansion()
- var/edge_check = result.len
+ if(item.in_stasis)
+ continue
- if(result.len>0)
- for(var/obj/machinery/atmospherics/pipe/item in result)
- if(!members.Find(item))
- members += item
- possible_expansions += item
+ if(!members.Find(item))
+ members += item
+ possible_expansions += item
- volume += item.volume
- item.parent = src
+ volume += item.volume
+ item.parent = src
- alert_pressure = min(alert_pressure, item.alert_pressure)
+ alert_pressure = min(alert_pressure, item.alert_pressure)
- if(item.air_temporary)
- air.merge(item.air_temporary)
+ if(item.air_temporary)
+ air.merge(item.air_temporary)
- edge_check--
+ if(item.leaking)
+ leaks |= item
- if(edge_check>0)
- edges += borderline
+ edge_check--
- possible_expansions -= borderline
+ if(edge_check>0)
+ edges += borderline
- air.volume = volume
+ possible_expansions -= borderline
- proc/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference)
+ air.volume = volume
- if(new_network.line_members.Find(src))
- return 0
+/datum/pipeline/proc/network_expand(datum/pipe_network/new_network, obj/machinery/atmospherics/pipe/reference)
- new_network.line_members += src
+ if(new_network.line_members.Find(src))
+ return 0
- network = new_network
+ new_network.line_members += src
- for(var/obj/machinery/atmospherics/pipe/edge in edges)
- for(var/obj/machinery/atmospherics/result in edge.pipeline_expansion())
- if(!istype(result,/obj/machinery/atmospherics/pipe) && (result!=reference))
- result.network_expand(new_network, edge)
+ network = new_network
+ network.leaks |= leaks
- return 1
+ for(var/obj/machinery/atmospherics/pipe/edge in edges)
+ for(var/obj/machinery/atmospherics/result in edge.pipeline_expansion())
+ if(!istype(result,/obj/machinery/atmospherics/pipe) && (result!=reference))
+ result.network_expand(new_network, edge)
- proc/return_network(obj/machinery/atmospherics/reference)
- if(!network)
- network = new /datum/pipe_network()
- network.build_network(src, null)
- //technically passing these parameters should not be allowed
- //however pipe_network.build_network(..) and pipeline.network_extend(...)
- // were setup to properly handle this case
+ return 1
- return network
+/datum/pipeline/proc/return_network(obj/machinery/atmospherics/reference)
+ if(!network)
+ network = new /datum/pipe_network()
+ network.build_network(src, null)
+ //technically passing these parameters should not be allowed
+ //however pipe_network.build_network(..) and pipeline.network_extend(...)
+ // were setup to properly handle this case
- proc/mingle_with_turf(turf/simulated/target, mingle_volume)
- var/datum/gas_mixture/air_sample = air.remove_ratio(mingle_volume/air.volume)
- air_sample.volume = mingle_volume
+ return network
- if(istype(target) && target.zone)
- //Have to consider preservation of group statuses
- var/datum/gas_mixture/turf_copy = new
- var/datum/gas_mixture/turf_original = new
+/datum/pipeline/proc/mingle_with_turf(turf/simulated/target, mingle_volume)
+ var/datum/gas_mixture/air_sample = air.remove_ratio(mingle_volume/air.volume)
+ air_sample.volume = mingle_volume
- 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)
+ if(istype(target) && target.zone)
+ //Have to consider preservation of group statuses
+ var/datum/gas_mixture/turf_copy = new
+ var/datum/gas_mixture/turf_original = new
- equalize_gases(list(air_sample, turf_copy))
- air.merge(air_sample)
+ 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)
- target.zone.air.remove(turf_original.total_moles)
- target.zone.air.merge(turf_copy)
+ target.zone.air.remove(turf_original.total_moles)
+ target.zone.air.merge(turf_copy)
- else
- var/datum/gas_mixture/turf_air = target.return_air()
+ else
+ var/datum/gas_mixture/turf_air = target.return_air()
- equalize_gases(list(air_sample, turf_air))
- air.merge(air_sample)
- //turf_air already modified by equalize_gases()
+ equalize_gases(list(air_sample, turf_air))
+ air.merge(air_sample)
+ //turf_air already modified by equalize_gases()
- if(network)
- network.update = 1
+ if(network)
+ network.update = 1
- proc/temperature_interact(turf/target, share_volume, thermal_conductivity)
- var/total_heat_capacity = air.heat_capacity()
- var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)
+/datum/pipeline/proc/temperature_interact(turf/target, share_volume, thermal_conductivity)
+ var/total_heat_capacity = air.heat_capacity()
+ var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)
- if(istype(target, /turf/simulated))
- var/turf/simulated/modeled_location = target
+ if(istype(target, /turf/simulated))
+ var/turf/simulated/modeled_location = target
- if(modeled_location.blocks_air)
+ if(modeled_location.blocks_air)
- if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
- var/delta_temperature = air.temperature - modeled_location.temperature
-
- var/heat = thermal_conductivity*delta_temperature* \
- (partial_heat_capacity*modeled_location.heat_capacity/(partial_heat_capacity+modeled_location.heat_capacity))
-
- air.temperature -= heat/total_heat_capacity
- modeled_location.temperature += heat/modeled_location.heat_capacity
-
- else
- var/delta_temperature = 0
- var/sharer_heat_capacity = 0
-
- if(modeled_location.zone)
- delta_temperature = (air.temperature - modeled_location.zone.air.temperature)
- sharer_heat_capacity = modeled_location.zone.air.heat_capacity()
- else
- delta_temperature = (air.temperature - modeled_location.air.temperature)
- sharer_heat_capacity = modeled_location.air.heat_capacity()
-
- var/self_temperature_delta = 0
- var/sharer_temperature_delta = 0
-
- if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
- var/heat = thermal_conductivity*delta_temperature* \
- (partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))
-
- self_temperature_delta = -heat/total_heat_capacity
- sharer_temperature_delta = heat/sharer_heat_capacity
- else
- return 1
-
- air.temperature += self_temperature_delta
-
- if(modeled_location.zone)
- modeled_location.zone.air.temperature += sharer_temperature_delta/modeled_location.zone.air.group_multiplier
- else
- modeled_location.air.temperature += sharer_temperature_delta
-
-
- else
- if((target.heat_capacity>0) && (partial_heat_capacity>0))
- var/delta_temperature = air.temperature - target.temperature
+ if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
+ var/delta_temperature = air.temperature - modeled_location.temperature
var/heat = thermal_conductivity*delta_temperature* \
- (partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
+ (partial_heat_capacity*modeled_location.heat_capacity/(partial_heat_capacity+modeled_location.heat_capacity))
air.temperature -= heat/total_heat_capacity
- if(network)
- network.update = 1
+ modeled_location.temperature += heat/modeled_location.heat_capacity
- //surface must be the surface area in m^2
- proc/radiate_heat_to_space(surface, thermal_conductivity)
- var/gas_density = air.total_moles/air.volume
- thermal_conductivity *= min(gas_density / ( RADIATOR_OPTIMUM_PRESSURE/(R_IDEAL_GAS_EQUATION*GAS_CRITICAL_TEMPERATURE) ), 1) //mult by density ratio
+ else
+ var/delta_temperature = 0
+ var/sharer_heat_capacity = 0
- // We only get heat from the star on the exposed surface area.
- // If the HE pipes gain more energy from AVERAGE_SOLAR_RADIATION than they can radiate, then they have a net heat increase.
- var/heat_gain = AVERAGE_SOLAR_RADIATION * (RADIATOR_EXPOSED_SURFACE_AREA_RATIO * surface) * thermal_conductivity
+ if(modeled_location.zone)
+ delta_temperature = (air.temperature - modeled_location.zone.air.temperature)
+ sharer_heat_capacity = modeled_location.zone.air.heat_capacity()
+ else
+ delta_temperature = (air.temperature - modeled_location.air.temperature)
+ sharer_heat_capacity = modeled_location.air.heat_capacity()
- // Previously, the temperature would enter equilibrium at 26C or 294K.
- // Only would happen if both sides (all 2 square meters of surface area) were exposed to sunlight. We now assume it aligned edge on.
- // It currently should stabilise at 129.6K or -143.6C
- heat_gain -= surface * STEFAN_BOLTZMANN_CONSTANT * thermal_conductivity * (air.temperature - COSMIC_RADIATION_TEMPERATURE) ** 4
+ var/self_temperature_delta = 0
+ var/sharer_temperature_delta = 0
- air.add_thermal_energy(heat_gain)
- if(network)
- network.update = 1
+ if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
+ var/heat = thermal_conductivity*delta_temperature* \
+ (partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))
+
+ self_temperature_delta = -heat/total_heat_capacity
+ sharer_temperature_delta = heat/sharer_heat_capacity
+ else
+ return 1
+
+ air.temperature += self_temperature_delta
+
+ if(modeled_location.zone)
+ modeled_location.zone.air.temperature += sharer_temperature_delta/modeled_location.zone.air.group_multiplier
+ else
+ modeled_location.air.temperature += sharer_temperature_delta
+
+
+ else
+ if((target.heat_capacity>0) && (partial_heat_capacity>0))
+ var/delta_temperature = air.temperature - target.temperature
+
+ var/heat = thermal_conductivity*delta_temperature* \
+ (partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
+
+ air.temperature -= heat/total_heat_capacity
+ if(network)
+ network.update = 1
+
+//surface must be the surface area in m^2
+/datum/pipeline/proc/radiate_heat_to_space(surface, thermal_conductivity)
+ var/gas_density = air.total_moles/air.volume
+ thermal_conductivity *= min(gas_density / ( RADIATOR_OPTIMUM_PRESSURE/(R_IDEAL_GAS_EQUATION*GAS_CRITICAL_TEMPERATURE) ), 1) //mult by density ratio
+
+ // We only get heat from the star on the exposed surface area.
+ // If the HE pipes gain more energy from AVERAGE_SOLAR_RADIATION than they can radiate, then they have a net heat increase.
+ var/heat_gain = AVERAGE_SOLAR_RADIATION * (RADIATOR_EXPOSED_SURFACE_AREA_RATIO * surface) * thermal_conductivity
+
+ // Previously, the temperature would enter equilibrium at 26C or 294K.
+ // Only would happen if both sides (all 2 square meters of surface area) were exposed to sunlight. We now assume it aligned edge on.
+ // It currently should stabilise at 129.6K or -143.6C
+ heat_gain -= surface * STEFAN_BOLTZMANN_CONSTANT * thermal_conductivity * (air.temperature - COSMIC_RADIATION_TEMPERATURE) ** 4
+
+ air.add_thermal_energy(heat_gain)
+ if(network)
+ network.update = 1
diff --git a/code/ATMOSPHERICS/pipes/he_pipes.dm b/code/ATMOSPHERICS/pipes/he_pipes.dm
index 2d77e3a4ca..1b283e3ba4 100644
--- a/code/ATMOSPHERICS/pipes/he_pipes.dm
+++ b/code/ATMOSPHERICS/pipes/he_pipes.dm
@@ -67,8 +67,22 @@
return
update_icon()
+ handle_leaking()
return
+/obj/machinery/atmospherics/pipe/simple/heat_exchanging/set_leaking(var/new_leaking) // They already process, no need for manual processing toggles.
+ if(new_leaking && !leaking)
+ leaking = TRUE
+ if(parent)
+ parent.leaks |= src
+ if(parent.network)
+ parent.network.leaks |= src
+ else if (!new_leaking && leaking)
+ leaking = FALSE
+ if(parent)
+ parent.leaks -= src
+ if(parent.network)
+ parent.network.leaks -= src
/obj/machinery/atmospherics/pipe/simple/heat_exchanging/process()
if(!parent)
@@ -180,4 +194,5 @@
return
update_icon()
+ handle_leaking()
return
diff --git a/code/ATMOSPHERICS/pipes/manifold.dm b/code/ATMOSPHERICS/pipes/manifold.dm
index 524d420d39..1af1eaa767 100644
--- a/code/ATMOSPHERICS/pipes/manifold.dm
+++ b/code/ATMOSPHERICS/pipes/manifold.dm
@@ -68,9 +68,16 @@
node3 = null
update_icon()
+ handle_leaking()
..()
+/obj/machinery/atmospherics/pipe/manifold/handle_leaking()
+ if(node1 && node2 && node3)
+ set_leaking(FALSE)
+ else
+ set_leaking(TRUE)
+
/obj/machinery/atmospherics/pipe/manifold/change_color(var/new_color)
..()
//for updating connected atmos device pipes (i.e. vents, manifolds, etc)
@@ -154,6 +161,7 @@
var/turf/T = get_turf(src)
if(level == 1 && !T.is_plating()) hide(1)
update_icon()
+ handle_leaking()
/obj/machinery/atmospherics/pipe/manifold/visible
icon_state = "map"
diff --git a/code/ATMOSPHERICS/pipes/manifold4w.dm b/code/ATMOSPHERICS/pipes/manifold4w.dm
index 0cc022423b..500088fec6 100644
--- a/code/ATMOSPHERICS/pipes/manifold4w.dm
+++ b/code/ATMOSPHERICS/pipes/manifold4w.dm
@@ -66,9 +66,16 @@
node4 = null
update_icon()
+ handle_leaking()
..()
+/obj/machinery/atmospherics/pipe/manifold4w/handle_leaking()
+ if(node1 && node2 && node3 && node4)
+ set_leaking(FALSE)
+ else
+ set_leaking(TRUE)
+
/obj/machinery/atmospherics/pipe/manifold4w/change_color(var/new_color)
..()
//for updating connected atmos device pipes (i.e. vents, manifolds, etc)
@@ -156,6 +163,7 @@
var/turf/T = get_turf(src)
if(level == 1 && !T.is_plating()) hide(1)
update_icon()
+ handle_leaking()
/obj/machinery/atmospherics/pipe/manifold4w/visible
icon_state = "map_4way"
diff --git a/code/ATMOSPHERICS/pipes/pipe_base.dm b/code/ATMOSPHERICS/pipes/pipe_base.dm
index a035857e54..d1bb92fac7 100644
--- a/code/ATMOSPHERICS/pipes/pipe_base.dm
+++ b/code/ATMOSPHERICS/pipes/pipe_base.dm
@@ -6,6 +6,7 @@
var/datum/gas_mixture/air_temporary // used when reconstructing a pipeline that broke
var/datum/pipeline/parent
var/volume = 0
+ var/leaking = FALSE // Do not set directly, use set_leaking(TRUE/FALSE)
layer = PIPES_LAYER
use_power = 0
@@ -13,6 +14,7 @@
pipe_flags = 0 // Does not have PIPING_DEFAULT_LAYER_ONLY flag.
var/alert_pressure = 80*ONE_ATMOSPHERE
+ var/in_stasis = FALSE
//minimum pressure before check_pressure(...) should be called
can_buckle = 1
@@ -30,6 +32,31 @@
/obj/machinery/atmospherics/pipe/hides_under_flooring()
return level != 2
+/obj/machinery/atmospherics/pipe/proc/set_leaking(var/new_leaking)
+ if(new_leaking && !leaking)
+ if(!speed_process)
+ START_MACHINE_PROCESSING(src)
+ else
+ START_PROCESSING(SSfastprocess, src)
+ leaking = TRUE
+ if(parent)
+ parent.leaks |= src
+ if(parent.network)
+ parent.network.leaks |= src
+ else if (!new_leaking && leaking)
+ if(!speed_process)
+ STOP_MACHINE_PROCESSING(src)
+ else
+ STOP_PROCESSING(SSfastprocess, src)
+ leaking = FALSE
+ if(parent)
+ parent.leaks -= src
+ if(parent.network)
+ parent.network.leaks -= src
+
+/obj/machinery/atmospherics/pipe/proc/handle_leaking() // Used specifically to update leaking status on different pipes.
+ return
+
/obj/machinery/atmospherics/pipe/proc/pipeline_expansion()
return null
diff --git a/code/ATMOSPHERICS/pipes/simple.dm b/code/ATMOSPHERICS/pipes/simple.dm
index 6aee81807e..eb980c3b47 100644
--- a/code/ATMOSPHERICS/pipes/simple.dm
+++ b/code/ATMOSPHERICS/pipes/simple.dm
@@ -1,6 +1,6 @@
//
// Simple Pipes - Just a tube, maybe bent
-//
+//
/obj/machinery/atmospherics/pipe/simple
icon = 'icons/atmos/pipes.dmi'
icon_state = ""
@@ -34,6 +34,14 @@
icon = null
alpha = 255
+/obj/machinery/atmospherics/pipe/simple/process()
+ if(!parent)
+ ..()
+ else if(leaking)
+ parent.mingle_with_turf(loc, volume)
+ else
+ . = PROCESS_KILL
+
/obj/machinery/atmospherics/pipe/simple/check_pressure(pressure)
var/datum/gas_mixture/environment = loc.return_air()
@@ -147,6 +155,7 @@
var/turf/T = loc
if(level == 1 && !T.is_plating()) hide(1)
update_icon()
+ handle_leaking()
/obj/machinery/atmospherics/pipe/simple/disconnect(obj/machinery/atmospherics/reference)
if(reference == node1)
@@ -160,9 +169,16 @@
node2 = null
update_icon()
+ handle_leaking()
return null
+/obj/machinery/atmospherics/pipe/simple/handle_leaking()
+ if(node1 && node2)
+ set_leaking(FALSE)
+ else
+ set_leaking(TRUE)
+
/obj/machinery/atmospherics/pipe/simple/visible
icon_state = "intact"
level = 2
diff --git a/code/ATMOSPHERICS/pipes/universal.dm b/code/ATMOSPHERICS/pipes/universal.dm
index 2d8bd09dff..00d2746947 100644
--- a/code/ATMOSPHERICS/pipes/universal.dm
+++ b/code/ATMOSPHERICS/pipes/universal.dm
@@ -48,7 +48,7 @@
construction_type = /obj/item/pipe/binary
pipe_state = "universal"
-/obj/machinery/atmospherics/pipe/simple/hidden/universal/update_icon(var/safety = 0)
+/obj/machinery/atmospherics/pipe/simple/hidden/universal/update_icon(var/safety = 0) // Doesn't leak. It's a special pipe.
if(!check_icon_cache())
return
diff --git a/code/game/machinery/atmoalter/clamp.dm b/code/game/machinery/atmoalter/clamp.dm
new file mode 100644
index 0000000000..318145c229
--- /dev/null
+++ b/code/game/machinery/atmoalter/clamp.dm
@@ -0,0 +1,154 @@
+//Good luck. --BlueNexus
+
+//Static version of the clamp
+/obj/machinery/clamp
+ name = "stasis clamp"
+ desc = "A magnetic clamp which can halt the flow of gas in a pipe, via a localised stasis field."
+ description_info = "Click-dragging this to yourself while adjacent will attempt to remove it from the pipe."
+ icon = 'icons/atmos/clamp.dmi'
+ icon_state = "pclamp0"
+ anchored = 1.0
+ var/obj/machinery/atmospherics/pipe/simple/target = null
+ var/open = 1
+
+ var/datum/pipe_network/network_node1
+ var/datum/pipe_network/network_node2
+
+/obj/machinery/clamp/New(loc, var/obj/machinery/atmospherics/pipe/simple/to_attach = null)
+ ..()
+ if(istype(to_attach))
+ target = to_attach
+ else
+ target = locate(/obj/machinery/atmospherics/pipe/simple) in loc
+ if(target)
+ update_networks()
+ dir = target.dir
+ return 1
+
+/obj/machinery/clamp/proc/update_networks()
+ if(!target)
+ return
+ else
+ var/obj/machinery/atmospherics/pipe/node1 = target.node1
+ var/obj/machinery/atmospherics/pipe/node2 = target.node2
+ if(istype(node1))
+ var/datum/pipeline/P1 = node1.parent
+ network_node1 = P1.network
+ if(istype(node2))
+ var/datum/pipeline/P2 = node2.parent
+ network_node2 = P2.network
+
+/obj/machinery/clamp/attack_hand(var/mob/user)
+ if(!target)
+ return FALSE
+ if(!open)
+ open()
+ else
+ close()
+ to_chat(user, "You turn [open ? "off" : "on"] \the [src]")
+ return TRUE
+
+/obj/machinery/clamp/Destroy()
+ if(!open)
+ spawn(-1) open()
+ . = ..()
+
+/obj/machinery/clamp/proc/open()
+ if(open || !target)
+ return 0
+
+ target.build_network()
+
+
+ if(network_node1&&network_node2)
+ network_node1.merge(network_node2)
+ network_node2 = network_node1
+
+ if(network_node1)
+ network_node1.update = 1
+ else if(network_node2)
+ network_node2.update = 1
+
+ update_networks()
+
+ open = 1
+ icon_state = "pclamp0"
+ target.in_stasis = 0
+ return 1
+
+/obj/machinery/clamp/proc/close()
+ if(!open)
+ return 0
+
+ qdel(target.parent)
+
+ if(network_node1)
+ qdel(network_node1)
+ if(network_node2)
+ qdel(network_node2)
+
+ var/obj/machinery/atmospherics/pipe/node1 = null
+ var/obj/machinery/atmospherics/pipe/node2 = null
+
+ if(target.node1)
+ target.node1.build_network()
+ node1 = target.node1
+ if(target.node2)
+ target.node2.build_network()
+ node2 = target.node2
+ if(istype(node1) && node1.parent)
+ var/datum/pipeline/P1 = node1.parent
+ P1.build_pipeline(node1)
+ qdel(P1)
+ if(istype(node2) && node2.parent)
+ var/datum/pipeline/P2 = node2.parent
+ P2.build_pipeline(node2)
+ qdel(P2)
+// P1.build_network()
+// P2.build_network()
+
+ open = 0
+ icon_state = "pclamp1"
+ target.in_stasis = 1
+
+ return 1
+
+/obj/machinery/clamp/MouseDrop(obj/over_object as obj)
+ if(!usr)
+ return
+
+ if(open && over_object == usr && Adjacent(usr))
+ to_chat(usr, "You begin to remove \the [src]...")
+ if (do_after(usr, 30, src))
+ to_chat(usr, "You have removed \the [src].")
+ var/obj/item/clamp/C = new/obj/item/clamp(src.loc)
+ C.forceMove(usr.loc)
+ if(ishuman(usr))
+ usr.put_in_hands(C)
+ qdel(src)
+ return
+ else
+ to_chat(usr, "You can't remove \the [src] while it's active!")
+
+/obj/item/clamp
+ name = "stasis clamp"
+ desc = "A magnetic clamp which can halt the flow of gas in a pipe, via a localised stasis field."
+ icon = 'icons/atmos/clamp.dmi'
+ icon_state = "pclamp0"
+ origin_tech = list(TECH_ENGINEERING = 4, TECH_MAGNET = 4)
+
+/obj/item/clamp/afterattack(var/atom/A, mob/user as mob, proximity)
+ if(!proximity)
+ return
+
+ if (istype(A, /obj/machinery/atmospherics/pipe/simple))
+ to_chat(user, "You begin to attach \the [src] to \the [A]...")
+ var/C = locate(/obj/machinery/clamp) in get_turf(A)
+ if (do_after(user, 30, src) && !C)
+ if(!user.unEquip(src))
+ return
+ to_chat(user, "You have attached \the [src] to \the [A].")
+ new/obj/machinery/clamp(A.loc, A)
+ qdel(src)
+ if(C)
+ to_chat(user, "\The [C] is already attached to the pipe at this location!")
diff --git a/code/game/machinery/pipe/pipe_recipes.dm b/code/game/machinery/pipe/pipe_recipes.dm
index f0ae483055..24661951e9 100644
--- a/code/game/machinery/pipe/pipe_recipes.dm
+++ b/code/game/machinery/pipe/pipe_recipes.dm
@@ -27,6 +27,7 @@ var/global/list/atmos_pipe_recipes = null
new /datum/pipe_recipe/pipe("Gas Pump", /obj/machinery/atmospherics/binary/pump),
new /datum/pipe_recipe/pipe("Pressure Regulator", /obj/machinery/atmospherics/binary/passive_gate),
new /datum/pipe_recipe/pipe("High Power Gas Pump",/obj/machinery/atmospherics/binary/pump/high_power),
+ new /datum/pipe_recipe/pipe("Automatic Shutoff Valve",/obj/machinery/atmospherics/valve/shutoff),
new /datum/pipe_recipe/pipe("Scrubber", /obj/machinery/atmospherics/unary/vent_scrubber),
new /datum/pipe_recipe/meter("Meter"),
new /datum/pipe_recipe/pipe("Gas Filter", /obj/machinery/atmospherics/trinary/atmos_filter),
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
index 8380c059ec..5be0f76db7 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm
@@ -11,6 +11,8 @@
starts_with = list(
/obj/item/clothing/accessory/storage/brown_vest,
/obj/item/blueprints,
+ /obj/item/clamp,
+ /obj/item/clamp,
/obj/item/clothing/under/rank/chief_engineer,
/obj/item/clothing/under/rank/chief_engineer/skirt,
/obj/item/clothing/head/hardhat/white,
@@ -125,6 +127,7 @@
/obj/item/clothing/suit/fire/firefighter,
/obj/item/device/flashlight,
/obj/item/weapon/extinguisher,
+ /obj/item/clamp,
/obj/item/device/radio/headset/headset_eng,
/obj/item/device/radio/headset/headset_eng/alt,
/obj/item/clothing/suit/storage/hazardvest,
diff --git a/html/changelogs/Mechoid - Leaky Pipes.yml b/html/changelogs/Mechoid - Leaky Pipes.yml
new file mode 100644
index 0000000000..62c477edb7
--- /dev/null
+++ b/html/changelogs/Mechoid - Leaky Pipes.yml
@@ -0,0 +1,37 @@
+################################
+# 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
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Mechoid
+
+# 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, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "Pipes will now leak if their ends are unsealed."
+ - rscadd: "Pipe clamps now exist."
diff --git a/icons/atmos/clamp.dmi b/icons/atmos/clamp.dmi
new file mode 100644
index 0000000000..ed22b93059
Binary files /dev/null and b/icons/atmos/clamp.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index 9452c0963d..179bbd6868 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -157,6 +157,7 @@
#include "code\ATMOSPHERICS\datum_pipeline.dm"
#include "code\ATMOSPHERICS\mainspipe.dm"
#include "code\ATMOSPHERICS\components\portables_connector.dm"
+#include "code\ATMOSPHERICS\components\shutoff.dm"
#include "code\ATMOSPHERICS\components\tvalve.dm"
#include "code\ATMOSPHERICS\components\valve.dm"
#include "code\ATMOSPHERICS\components\binary_devices\algae_generator_vr.dm"
@@ -761,6 +762,7 @@
#include "code\game\machinery\atmoalter\area_atmos_computer.dm"
#include "code\game\machinery\atmoalter\area_atmos_computer_vr.dm"
#include "code\game\machinery\atmoalter\canister.dm"
+#include "code\game\machinery\atmoalter\clamp.dm"
#include "code\game\machinery\atmoalter\meter.dm"
#include "code\game\machinery\atmoalter\portable_atmospherics.dm"
#include "code\game\machinery\atmoalter\pump.dm"