diff --git a/baystation12.dme b/baystation12.dme
index 9028d38205b..99c48599971 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -1734,6 +1734,9 @@
#include "code\modules\vehicles\cargo_train.dm"
#include "code\modules\vehicles\train.dm"
#include "code\modules\vehicles\vehicle.dm"
+#include "code\modules\ventcrawl\ventcrawl.dm"
+#include "code\modules\ventcrawl\ventcrawl_atmospherics.dm"
+#include "code\modules\ventcrawl\ventcrawl_verb.dm"
#include "code\modules\virus2\admin.dm"
#include "code\modules\virus2\analyser.dm"
#include "code\modules\virus2\antibodies.dm"
diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index 89cc53b0d42..895aa17f09e 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -29,6 +29,8 @@ Pipelines + Other Objects -> Pipe network
var/pipe_color
var/global/datum/pipe_icon_manager/icon_manager
+ var/obj/machinery/atmospherics/node1
+ var/obj/machinery/atmospherics/node2
/obj/machinery/atmospherics/New()
if(!icon_manager)
diff --git a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
index 37985034b1c..2ccfe0f2e87 100644
--- a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
@@ -6,9 +6,6 @@ obj/machinery/atmospherics/binary
var/datum/gas_mixture/air1
var/datum/gas_mixture/air2
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network1
var/datum/pipe_network/network2
diff --git a/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm b/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
index aead60a3827..585c4a8dde6 100644
--- a/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/pipeturbine.dm
@@ -17,9 +17,6 @@
var/dP = 0
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network1
var/datum/pipe_network/network2
diff --git a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
index 3f5d66f2620..fa066d978f7 100644
--- a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
+++ b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
@@ -7,8 +7,6 @@ obj/machinery/atmospherics/trinary
var/datum/gas_mixture/air2
var/datum/gas_mixture/air3
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/datum/pipe_network/network1
diff --git a/code/ATMOSPHERICS/components/tvalve.dm b/code/ATMOSPHERICS/components/tvalve.dm
index 42556232bdd..d9d85850d59 100644
--- a/code/ATMOSPHERICS/components/tvalve.dm
+++ b/code/ATMOSPHERICS/components/tvalve.dm
@@ -12,8 +12,6 @@
var/state = 0 // 0 = go straight, 1 = go to side
// like a trinary component, node1 is input, node2 is side output, node3 is straight output
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/datum/pipe_network/network_node1
diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm
index a372f139e85..0df3f443433 100644
--- a/code/ATMOSPHERICS/components/unary/vent_pump.dm
+++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm
@@ -378,6 +378,11 @@
else
..()
+/obj/machinery/atmospherics/unary/vent_pump/proc/is_welded()
+ if (welded > 0)
+ return 1
+ return 0
+
/obj/machinery/atmospherics/unary/vent_pump/examine(mob/user)
if(..(user, 1))
user << "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
diff --git a/code/ATMOSPHERICS/components/valve.dm b/code/ATMOSPHERICS/components/valve.dm
index 49ae4af2cf2..33d4380acda 100644
--- a/code/ATMOSPHERICS/components/valve.dm
+++ b/code/ATMOSPHERICS/components/valve.dm
@@ -12,9 +12,6 @@
var/open = 0
var/openDuringInit = 0
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/datum/pipe_network/network_node1
var/datum/pipe_network/network_node2
diff --git a/code/ATMOSPHERICS/pipes.dm b/code/ATMOSPHERICS/pipes.dm
index 2315e856fd1..fd3b3aa9c5d 100644
--- a/code/ATMOSPHERICS/pipes.dm
+++ b/code/ATMOSPHERICS/pipes.dm
@@ -153,9 +153,6 @@
dir = SOUTH
initialize_directions = SOUTH|NORTH
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
-
var/minimum_temperature_difference = 300
var/thermal_conductivity = 0 //WALL_HEAT_TRANSFER_COEFFICIENT No
@@ -423,8 +420,6 @@
dir = SOUTH
initialize_directions = EAST|NORTH|WEST
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
level = 1
@@ -679,8 +674,6 @@
dir = SOUTH
initialize_directions = NORTH|SOUTH|EAST|WEST
- var/obj/machinery/atmospherics/node1
- var/obj/machinery/atmospherics/node2
var/obj/machinery/atmospherics/node3
var/obj/machinery/atmospherics/node4
@@ -1061,8 +1054,6 @@
initialize_directions = SOUTH
density = 1
- var/obj/machinery/atmospherics/node1
-
/obj/machinery/atmospherics/pipe/tank/New()
icon_state = "air"
initialize_directions = dir
@@ -1237,8 +1228,6 @@
var/build_killswitch = 1
- var/obj/machinery/atmospherics/node1
-
/obj/machinery/atmospherics/pipe/vent/New()
initialize_directions = dir
..()
diff --git a/code/TriDimension/Pipes.dm b/code/TriDimension/Pipes.dm
index c71255a34bd..7072951dba8 100644
--- a/code/TriDimension/Pipes.dm
+++ b/code/TriDimension/Pipes.dm
@@ -13,9 +13,6 @@ obj/machinery/atmospherics/pipe/zpipe
dir = SOUTH
initialize_directions = SOUTH
- var/obj/machinery/atmospherics/node1 //connection on the same Z
- var/obj/machinery/atmospherics/node2 //connection on the other Z
-
var/minimum_temperature_difference = 300
var/thermal_conductivity = 0 //WALL_HEAT_TRANSFER_COEFFICIENT No
diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm
index 7b0ef35eafa..c85bbe67384 100644
--- a/code/modules/mob/living/carbon/alien/life.dm
+++ b/code/modules/mob/living/carbon/alien/life.dm
@@ -116,6 +116,9 @@
/mob/living/carbon/alien/proc/handle_regular_hud_updates()
+ if(is_ventcrawling == 0) // Stops sight returning to normal if inside a vent
+ sight = species.vision_flags
+
if (stat == 2 || (XRAY in src.mutations))
sight |= SEE_TURFS
sight |= SEE_MOBS
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 4bc89cbc12d..c946099f10a 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -1248,9 +1248,10 @@
*/
else
- sight = species.vision_flags
- see_in_dark = species.darksight
- see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : SEE_INVISIBLE_LIVING
+ if(is_ventcrawling == 0) // Stops sight returning to normal if inside a vent
+ sight = species.vision_flags
+ see_in_dark = species.darksight
+ see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : SEE_INVISIBLE_LIVING
if(XRAY in mutations)
sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
diff --git a/code/modules/mob/living/carbon/metroid/life.dm b/code/modules/mob/living/carbon/metroid/life.dm
index 1c76cf2e3ca..56857dc74ad 100644
--- a/code/modules/mob/living/carbon/metroid/life.dm
+++ b/code/modules/mob/living/carbon/metroid/life.dm
@@ -11,6 +11,9 @@
handle_chemicals_in_body()
handle_nutrition()
+ if(is_ventcrawling == 0) // Stops sight returning to normal if inside a vent
+ sight = SEE_SELF
+
if (!client)
handle_targets()
if (!AIproc)
@@ -58,7 +61,7 @@
adjustToxLoss(200)
else
adjustToxLoss(30)
-
+
updatehealth()
return //TODO: DEFERRED
diff --git a/code/modules/mob/living/carbon/metroid/metroid.dm b/code/modules/mob/living/carbon/metroid/metroid.dm
index eedb68e68d1..dee6d36b90a 100644
--- a/code/modules/mob/living/carbon/metroid/metroid.dm
+++ b/code/modules/mob/living/carbon/metroid/metroid.dm
@@ -410,9 +410,4 @@
if(powerlevel > 10)
powerlevel = 10
adjustToxLoss(-10)
- nutrition = max(nutrition, get_max_nutrition())
-
-/mob/living/carbon/slime/cannot_use_vents()
- if(Victim)
- return "You cannot ventcrawl while feeding."
- ..()
\ No newline at end of file
+ nutrition = max(nutrition, get_max_nutrition())
\ No newline at end of file
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index c14e49154e2..98f4e191691 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -664,114 +664,6 @@ default behaviour is:
resting = !resting
src << "You are now [resting ? "resting" : "getting up"]"
-/mob/living/proc/is_allowed_vent_crawl_item(var/obj/item/carried_item)
- return isnull(get_inventory_slot(carried_item))
-
-/mob/living/simple_animal/spiderbot/is_allowed_vent_crawl_item(var/obj/item/carried_item)
- if(carried_item == held_item)
- return 0
- return ..()
-
-/mob/living/proc/handle_ventcrawl(var/obj/machinery/atmospherics/unary/vent_pump/vent_found = null, var/ignore_items = 0) // -- TLE -- Merged by Carn
- if(stat)
- src << "You must be conscious to do this!"
- return
- if(lying)
- src << "You can't vent crawl while you're stunned!"
- return
-
- var/special_fail_msg = cannot_use_vents()
- if(special_fail_msg)
- src << "[special_fail_msg]"
- return
-
- if(vent_found) // one was passed in, probably from vent/AltClick()
- if(vent_found.welded)
- src << "That vent is welded shut."
- return
- if(!vent_found.Adjacent(src))
- return // don't even acknowledge that
- else
- for(var/obj/machinery/atmospherics/unary/vent_pump/v in range(1,src))
- if(!v.welded)
- if(v.Adjacent(src))
- vent_found = v
- if(!vent_found)
- src << "You'll need a non-welded vent to crawl into!"
- return
-
- if(!vent_found.network || !vent_found.network.normal_members.len)
- src << "This vent is not connected to anything."
- return
-
- var/list/vents = list()
- for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in vent_found.network.normal_members)
- if(temp_vent.welded)
- continue
- if(temp_vent in loc)
- continue
- var/turf/T = get_turf(temp_vent)
-
- if(!T || T.z != loc.z)
- continue
-
- var/i = 1
- var/index = "[T.loc.name]\[[i]\]"
- while(index in vents)
- i++
- index = "[T.loc.name]\[[i]\]"
- vents[index] = temp_vent
- if(!vents.len)
- src << "\red There are no available vents to travel to, they could be welded."
- return
-
- var/obj/selection = input("Select a destination.", "Duct System") as null|anything in sortAssoc(vents)
- if(!selection) return
-
- if(!vent_found.Adjacent(src))
- src << "Never mind, you left."
- return
-
- if(!ignore_items)
- for(var/obj/item/carried_item in contents)//If the monkey got on objects.
- if(is_allowed_vent_crawl_item(carried_item))
- continue
- src << "You can't be carrying items or have items equipped when vent crawling!"
- return
-
- if(isslime(src))
- var/mob/living/carbon/slime/S = src
- if(S.Victim)
- src << "\red You'll have to let [S.Victim] go or finish eating \him first."
- return
-
- var/obj/machinery/atmospherics/unary/vent_pump/target_vent = vents[selection]
- if(!target_vent)
- return
-
- for(var/mob/O in viewers(src, null))
- O.show_message(text("[src] scrambles into the ventillation ducts!"), 1)
- loc = target_vent
-
- var/travel_time = round(get_dist(loc, target_vent.loc) / 2)
-
- spawn(travel_time)
-
- if(!target_vent) return
- for(var/mob/O in hearers(target_vent,null))
- O.show_message("You hear something squeezing through the ventilation ducts.",2)
-
- sleep(travel_time)
-
- if(!target_vent) return
- if(target_vent.welded) //the vent can be welded while alien scrolled through the list or travelled.
- target_vent = vent_found //travel back. No additional time required.
- src << "\red The vent you were heading to appears to be welded."
- loc = target_vent.loc
- var/area/new_area = get_area(loc)
- if(new_area)
- new_area.Entered(src)
-
/mob/living/proc/cannot_use_vents()
return "You can't fit into that vent."
diff --git a/code/modules/mob/living/living_powers.dm b/code/modules/mob/living/living_powers.dm
index 821cb054b46..b7d640a5ee8 100644
--- a/code/modules/mob/living/living_powers.dm
+++ b/code/modules/mob/living/living_powers.dm
@@ -1,13 +1,3 @@
-/mob/living/proc/ventcrawl()
- set name = "Crawl through Vent"
- set desc = "Enter an air vent and crawl through the pipe system."
- set category = "Abilities"
-
- if(stat == DEAD || paralysis || weakened || stunned || restrained())
- return
-
- handle_ventcrawl()
-
/mob/living/proc/hide()
set name = "Hide"
set desc = "Allows to hide beneath tables or certain items. Toggled on or off."
diff --git a/code/modules/ventcrawl/ventcrawl.dm b/code/modules/ventcrawl/ventcrawl.dm
new file mode 100644
index 00000000000..d23ca1ad75a
--- /dev/null
+++ b/code/modules/ventcrawl/ventcrawl.dm
@@ -0,0 +1,223 @@
+var/list/ventcrawl_machinery = list(
+ /obj/machinery/atmospherics/unary/vent_pump,
+ /obj/machinery/atmospherics/unary/vent_scrubber
+ )
+
+// Vent crawling whitelisted items, whoo
+/mob/living/var/list/can_enter_vent_with = list(
+ /obj/item/weapon/implant,
+ /obj/item/device/radio/borg,
+ /obj/item/weapon/holder,
+ /obj/machinery/camera,
+ /mob/living/simple_animal/borer
+ )
+
+/mob/living/var/list/icon/pipes_shown = list()
+/mob/living/var/last_played_vent
+/mob/living/var/is_ventcrawling = 0
+/mob/living/var/next_play_vent = 0
+
+/mob/living/proc/can_ventcrawl()
+ return 0
+
+/mob/living/Login()
+ . = ..()
+ //login during ventcrawl
+ if(is_ventcrawling && istype(loc, /obj/machinery/atmospherics)) //attach us back into the pipes
+ remove_ventcrawl()
+ add_ventcrawl(loc)
+
+/mob/living/carbon/slime/can_ventcrawl()
+ if(Victim)
+ src << "You cannot ventcrawl while feeding."
+ return 0
+ return 1
+
+/mob/living/proc/is_allowed_vent_crawl_item(var/obj/item/carried_item)
+ for(var/type in can_enter_vent_with)
+ if(istype(carried_item, can_enter_vent_with))
+ return get_inventory_slot(carried_item) == 0
+ return 0
+
+/mob/living/carbon/is_allowed_vent_crawl_item(var/obj/item/carried_item)
+ if(carried_item in internal_organs)
+ return 1
+ return ..()
+
+/mob/living/carbon/human/is_allowed_vent_crawl_item(var/obj/item/carried_item)
+ if(carried_item in organs)
+ return 1
+ return ..()
+
+/mob/living/simple_animal/spiderbot/is_allowed_vent_crawl_item(var/obj/item/carried_item)
+ if(carried_item == held_item)
+ return 1
+ return ..()
+
+/mob/living/proc/ventcrawl_carry()
+ for(var/atom/A in src.contents)
+ if(!is_allowed_vent_crawl_item(A))
+ src << "You can't be carrying that when vent crawling!"
+ return 0
+ return 1
+
+/obj/machinery/atmospherics/AltClick(mob/living/user)
+ if(is_type_in_list(src,ventcrawl_machinery) && user.can_ventcrawl())
+ user.handle_ventcrawl()
+ return 1
+ return ..()
+
+/mob/living/carbon/human/can_ventcrawl()
+ return issmall(src)
+
+/mob/proc/start_ventcrawl()
+ var/atom/pipe
+ var/list/pipes = list()
+ for(var/obj/machinery/atmospherics/unary/U in range(1))
+ if(is_type_in_list(U,ventcrawl_machinery) && Adjacent(U))
+ pipes |= U
+ if(!pipes || !pipes.len)
+ usr << "There are no pipes that you can ventcrawl into within range!"
+ return
+ if(pipes.len == 1)
+ pipe = pipes[1]
+ else
+ pipe = input("Crawl Through Vent", "Pick a pipe") as null|anything in pipes
+ if(canmove && pipe)
+ return pipe
+
+/mob/living/carbon/slime/can_ventcrawl()
+ return 1
+
+/mob/living/simple_animal/borer/can_ventcrawl()
+ return 1
+
+/mob/living/simple_animal/borer/ventcrawl_carry()
+ return 1
+
+/mob/living/simple_animal/mouse/can_ventcrawl()
+ return 1
+
+/mob/living/simple_animal/spiderbot/can_ventcrawl()
+ return 1
+
+/mob/living/carbon/alien/can_ventcrawl()
+ return 1
+
+/mob/living/carbon/alien/ventcrawl_carry()
+ return 1
+
+/mob/living/proc/size_to_crawldelay(var/size)
+ var/delayticks = size * 3
+ return delayticks >= 3 ? delayticks : 3
+
+/mob/living/var/ventcrawl_layer = 3
+
+/mob/living/proc/handle_ventcrawl(var/atom/clicked_on)
+
+ if(!stat)
+ if(!lying)
+
+ var/obj/machinery/atmospherics/unary/vent_found
+
+ if(clicked_on && Adjacent(clicked_on))
+ vent_found = clicked_on
+ if(!istype(vent_found) || !vent_found.can_crawl_through())
+ vent_found = null
+
+ if(!vent_found)
+ for(var/obj/machinery/atmospherics/machine in range(1,src))
+ if(is_type_in_list(machine, ventcrawl_machinery))
+ vent_found = machine
+
+ if(!vent_found || !vent_found.can_crawl_through())
+ vent_found = null
+
+ if(vent_found)
+ break
+
+ if(istype(vent_found, /obj/machinery/atmospherics/unary/vent_pump/) && vent_found:is_welded()) // welded check
+ src << "You can't crawl into a welded vent!"
+ return
+
+ if(vent_found)
+ if(vent_found.network && (vent_found.network.normal_members.len || vent_found.network.line_members.len))
+
+ visible_message("[src] begins to climb into the ventilation system!","You begin climbing into the ventilation system...")
+ if(vent_found.air_contents && !issilicon(src))
+
+ switch(vent_found.air_contents.temperature)
+ if(0 to BODYTEMP_COLD_DAMAGE_LIMIT)
+ src << "You feel a painful freeze coming from the vent!"
+ if(BODYTEMP_COLD_DAMAGE_LIMIT to T0C)
+ src << "You feel an icy chill coming from the vent."
+ if(T0C + 40 to BODYTEMP_HEAT_DAMAGE_LIMIT)
+ src << "You feel a hot wash coming from the vent."
+ if(BODYTEMP_HEAT_DAMAGE_LIMIT to INFINITY)
+ src << "You feel a searing heat coming from the vent!"
+
+ switch(vent_found.air_contents.return_pressure())
+ if(0 to HAZARD_LOW_PRESSURE)
+ src << "You feel a rushing draw pulling you into the vent!"
+ if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE)
+ src << "You feel a strong drag pulling you into the vent."
+ if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE)
+ src << "You feel a strong current pushing you away from the vent."
+ if(HAZARD_HIGH_PRESSURE to INFINITY)
+ src << "You feel a roaring wind pushing you away from the vent!"
+
+ if(!do_mob(src, vent_found, mob_size ? size_to_crawldelay(mob_size) : size_to_crawldelay(15), 1, 1))
+ return
+
+ if(!client)
+ return
+
+ if(!ventcrawl_carry())
+ return
+
+ visible_message("[src] scrambles into the ventilation ducts!", "You climb into the ventilation system.")
+
+ forceMove(vent_found)
+ add_ventcrawl(vent_found)
+ sight = (SEE_TURFS|BLIND)
+
+ else
+ src << "This vent is not connected to anything."
+
+ else
+ src << "You must be standing on or beside an air vent to enter it."
+
+ else
+ src << "You can't vent crawl while you're stunned!"
+
+ else
+ src << "You must be conscious to do this!"
+ return
+
+/mob/living/proc/add_ventcrawl(obj/machinery/atmospherics/starting_machine)
+ is_ventcrawling = 1
+ //candrop = 0
+ var/datum/pipe_network/network = starting_machine.return_network(starting_machine)
+ if(!network)
+ return
+ for(var/datum/pipeline/pipeline in network.line_members)
+ for(var/obj/machinery/atmospherics/A in (pipeline.members || pipeline.edges)) // Adds pipe and manifold images
+ if(!A.pipe_image)
+ A.pipe_image = image(A, A.loc, layer = 20, dir = A.dir)
+ pipes_shown += A.pipe_image
+ client.images += A.pipe_image
+ for (var/obj/machinery/atmospherics/V in network.normal_members) // Adds vent and scrubber images
+ if (!V.pipe_image || istype(V, /obj/machinery/atmospherics/unary/vent_pump/))
+ V.pipe_image = image(V, V.loc, layer = 20, dir = V.dir)
+ pipes_shown += V.pipe_image
+ client.images += V.pipe_image
+
+/mob/living/proc/remove_ventcrawl()
+ is_ventcrawling = 0
+ //candrop = 1
+ if(client)
+ for(var/image/current_image in pipes_shown)
+ client.images -= current_image
+ client.eye = src
+
+ pipes_shown.len = 0
\ No newline at end of file
diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
new file mode 100644
index 00000000000..1f8efdfd250
--- /dev/null
+++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
@@ -0,0 +1,90 @@
+/obj/machinery/atmospherics/var/image/pipe_image
+
+/obj/machinery/atmospherics/Destroy()
+ for(var/mob/living/M in src) //ventcrawling is serious business
+ M.remove_ventcrawl()
+ M.forceMove(get_turf(src))
+ if(pipe_image)
+ for(var/mob/living/M in player_list)
+ if(M.client)
+ M.client.images -= pipe_image
+ M.pipes_shown -= pipe_image
+ pipe_image = null
+ . = ..()
+
+/obj/machinery/atmospherics/ex_act(severity)
+ for(var/atom/movable/A in src) //ventcrawling is serious business
+ A.ex_act(severity)
+ . = ..()
+
+/obj/machinery/atmospherics/Entered(atom/movable/Obj)
+ if(istype(Obj, /mob/living))
+ var/mob/living/L = Obj
+ L.ventcrawl_layer = layer
+ . = ..()
+
+/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
+ if(!(direction & initialize_directions)) //can't go in a way we aren't connecting to
+ return
+ ventcrawl_to(user,findConnecting(direction, user.ventcrawl_layer),direction)
+
+/obj/machinery/atmospherics/proc/ventcrawl_to(var/mob/living/user, var/obj/machinery/atmospherics/target_move, var/direction)
+ if(target_move)
+ if(is_type_in_list(target_move, ventcrawl_machinery) && target_move.can_crawl_through())
+ if(istype(target_move, /obj/machinery/atmospherics/unary/vent_pump/) && target_move:is_welded())
+ user.visible_message("", "You can't escape from a welded vent.")
+ else
+ user.remove_ventcrawl()
+ user.forceMove(target_move.loc) //handles entering and so on
+ user.sight &= ~(SEE_TURFS|BLIND)
+ user.visible_message("You hear something squeezing through the ducts.", "You climb out the ventilation system.")
+ else if(target_move.can_crawl_through())
+ if(target_move.return_network(target_move) != return_network(src))
+ user.remove_ventcrawl()
+ user.add_ventcrawl(target_move)
+ user.forceMove(target_move)
+ user.client.eye = target_move //if we don't do this, Byond only updates the eye every tick - required for smooth movement
+ if(world.time > user.next_play_vent)
+ user.next_play_vent = world.time+30
+ playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
+ else
+ if((direction & initialize_directions) || is_type_in_list(src, ventcrawl_machinery) && src.can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent
+ user.remove_ventcrawl()
+ user.forceMove(src.loc)
+ user.visible_message("You hear something squeezing through the pipes.", "You climb out the ventilation system.")
+ user.canmove = 0
+ spawn(1)
+ user.canmove = 1
+
+/obj/machinery/atmospherics/proc/can_crawl_through()
+ return 1
+
+/obj/machinery/atmospherics/proc/findConnecting(var/direction)
+ for(var/obj/machinery/atmospherics/target in get_step(src,direction))
+ if(target.initialize_directions & get_dir(target,src))
+ if(isConnectable(target) && target.isConnectable(src))
+ return target
+
+/obj/machinery/atmospherics/proc/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node1 || target == node2)
+
+/obj/machinery/atmospherics/pipe/manifold/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node3 || ..())
+
+obj/machinery/atmospherics/trinary/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node3 || ..())
+
+/obj/machinery/atmospherics/pipe/manifold4w/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node3 || target == node4 || ..())
+
+/obj/machinery/atmospherics/tvalve/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node3 || ..())
+
+/obj/machinery/atmospherics/pipe/cap/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node || ..())
+
+/obj/machinery/atmospherics/portables_connector/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node || ..())
+
+/obj/machinery/atmospherics/unary/isConnectable(var/obj/machinery/atmospherics/target)
+ return (target == node || ..())
\ No newline at end of file
diff --git a/code/modules/ventcrawl/ventcrawl_verb.dm b/code/modules/ventcrawl/ventcrawl_verb.dm
new file mode 100644
index 00000000000..bdc71660073
--- /dev/null
+++ b/code/modules/ventcrawl/ventcrawl_verb.dm
@@ -0,0 +1,9 @@
+/mob/living/proc/ventcrawl()
+ set name = "Crawl through Vent"
+ set desc = "Enter an air vent and crawl through the pipe system."
+ set category = "Abilities"
+ if(paralysis || restrained())
+ return
+ var/pipe = start_ventcrawl()
+ if(pipe)
+ handle_ventcrawl()
\ No newline at end of file
diff --git a/html/changelogs/Bedshaped-PR-852.yml b/html/changelogs/Bedshaped-PR-852.yml
new file mode 100644
index 00000000000..df9fee25625
--- /dev/null
+++ b/html/changelogs/Bedshaped-PR-852.yml
@@ -0,0 +1,6 @@
+author: Bedshaped
+
+delete-after: True
+
+changes:
+ - rscadd: "Ported Bay12's ventcrawling by Zuhayr, originally from vg."
diff --git a/sound/machines/ventcrawl.ogg b/sound/machines/ventcrawl.ogg
new file mode 100644
index 00000000000..2df8b5c8981
Binary files /dev/null and b/sound/machines/ventcrawl.ogg differ