From 395f618b0f2f614ca96bc8c59eb5ffbe032c354a Mon Sep 17 00:00:00 2001
From: Will <7099514+Willburd@users.noreply.github.com>
Date: Fri, 7 Nov 2025 08:29:14 -0500
Subject: [PATCH] Ventcrawler update (#18709)
* mob atmos properly handled
* Ventcrawling signals
* ventcrawler blowout
* clarity
* fix
* fix again
* again
* consistency
* Teshari rattler
* scrubbers can be welded shut
---
code/ATMOSPHERICS/atmospherics.dm | 19 ++++
.../components/unary/unary_base.dm | 2 +-
.../components/unary/vent_scrubber.dm | 30 +++++-
code/__defines/dcs/signals.dm | 18 ++++
code/datums/ghost_spawn_options.dm | 2 +
code/modules/mob/living/simple_mob/life.dm | 100 ++++++++----------
.../simple_mob/subtypes/vore/demon/demon.dm | 15 +--
.../simple_mob/subtypes/vore/demon/demonAI.dm | 12 +--
.../subtypes/vore/shadekin/shadekin.dm | 5 -
code/modules/mob/mob_defines.dm | 1 +
code/modules/ventcrawl/ventcrawl.dm | 34 +++---
.../ventcrawl/ventcrawl_atmospherics.dm | 5 +
code/modules/vore/eating/belly_obj_vr.dm | 5 +
icons/atmos/vent_scrubber.dmi | Bin 8106 -> 8207 bytes
14 files changed, 155 insertions(+), 93 deletions(-)
diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index 71c55554a3..1f598436a7 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -260,3 +260,22 @@ Pipelines + Other Objects -> Pipe network
if(user.buckled)
user.buckled.unbuckle_mob(user, TRUE)
user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinal)), pressures / 250, pressures / 1250)
+
+/// Blows out a pipe, deconstructing it, breaking the floor and releasing all mobs crawling inside it
+/obj/machinery/atmospherics/proc/blowout(mob/user)
+ // Deconstruct turf
+ var/turf/our_turf = loc
+ if(!our_turf.is_plating() && istype(our_turf,/turf/simulated/floor)) //intact floor, pop the tile
+ var/turf/simulated/floor/our_floor = our_turf
+ our_floor.make_plating(TRUE)
+ // Deconstruct pipe
+ var/datum/gas_mixture/int_air = return_air()
+ var/datum/gas_mixture/env_air = our_turf.return_air()
+ var/internal_pressure = int_air.return_pressure()-env_air.return_pressure()
+ deconstruct()
+ // Release pressure
+ playsound(our_turf, 'sound/effects/bang.ogg', 70, 0, 0)
+ playsound(our_turf, 'sound/effects/clang2.ogg', 70, 0, 0)
+ if(internal_pressure > 2*ONE_ATMOSPHERE)
+ unsafe_pressure_release(user, internal_pressure)
+ playsound(our_turf, 'sound/machines/hiss.ogg', 50, 0, 0)
diff --git a/code/ATMOSPHERICS/components/unary/unary_base.dm b/code/ATMOSPHERICS/components/unary/unary_base.dm
index 09c1e2d9c9..19c698d742 100644
--- a/code/ATMOSPHERICS/components/unary/unary_base.dm
+++ b/code/ATMOSPHERICS/components/unary/unary_base.dm
@@ -11,7 +11,7 @@
var/datum/pipe_network/network
- var/welded = 0 //defining this here for ventcrawl stuff
+ var/welded = FALSE //defining this here for ventcrawl stuff
/obj/machinery/atmospherics/unary/Initialize(mapload)
. = ..()
diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
index 23a48b8a51..286576890e 100644
--- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
+++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
@@ -68,7 +68,9 @@
if(!istype(T))
return
- if(!powered())
+ if(welded)
+ scrubber_icon += "weld"
+ else if(!powered())
scrubber_icon += "off"
else
scrubber_icon += "[use_power ? "[scrubbing ? "on" : "in"]" : "off"]"
@@ -269,6 +271,27 @@
update_icon()
/obj/machinery/atmospherics/unary/vent_scrubber/attackby(var/obj/item/W as obj, var/mob/user as mob)
+ if(W.has_tool_quality(TOOL_WELDER))
+ var/obj/item/weldingtool/WT = W
+ if (WT.remove_fuel(0,user))
+ to_chat(user, span_notice("Now welding the vent."))
+
+ if(do_after(user, 20 * WT.toolspeed))
+ if(!src || !WT.isOn()) return
+ playsound(src, WT.usesound, 50, 1)
+ if(!welded)
+ user.visible_message(span_notice("\The [user] welds the vent shut."), span_notice("You weld the vent shut."), "You hear welding.")
+ welded = TRUE
+ update_icon()
+ else
+ user.visible_message(span_notice("[user] unwelds the vent."), span_notice("You unweld the vent."), "You hear welding.")
+ welded = FALSE
+ update_icon()
+ else
+ to_chat(user, span_notice("The welding tool needs to be on to start this task."))
+ else
+ to_chat(user, span_warning("You need more welding fuel to complete this task."))
+ return 1
if (!W.has_tool_quality(TOOL_WRENCH))
return ..()
if (!(stat & NOPOWER) && use_power)
@@ -278,6 +301,9 @@
if (node && node.level==1 && isturf(T) && !T.is_plating())
to_chat(user, span_warning("You must remove the plating first."))
return 1
+ if(welded)
+ to_chat(user, span_warning("You cannot unwrench \the [src], it is welded down firmly."))
+ return 1
if(!can_unwrench())
to_chat(user, span_warning("You cannot unwrench \the [src], it is too exerted due to internal pressure."))
add_fingerprint(user)
@@ -297,3 +323,5 @@
. += "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
else
. += "You are too far away to read the gauge."
+ if(welded)
+ . += "It is welded shut."
diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm
index a965c3b470..9de4f014a2 100644
--- a/code/__defines/dcs/signals.dm
+++ b/code/__defines/dcs/signals.dm
@@ -822,6 +822,24 @@
///called when you clear a mood event from anywhere in the code.
#define COMSIG_CLEAR_MOOD_EVENT "clear_mood"
+//Ventcrawling
+
+///called when a ventcrawling mob checks if it can begin ventcrawling : (obj/machinery/atmospherics/unary/vent_entered)
+#define COMSIG_MOB_VENTCRAWL_CHECK "ventcrawl_check"
+///called when a ventcrawling mob checks if it can enter a vent : (mob/entering_mob)
+#define COMSIG_VENT_CRAWLER_CHECK "ventcrawl_check"
+ #define VENT_CRAWL_BLOCK_ENTRY (1<<0)
+
+///called when a ventcrawling mob enters a vent : (obj/machinery/atmospherics/unary/vent_entered)
+#define COMSIG_MOB_VENTCRAWL_START "ventcrawl_start"
+///called when a ventcrawling mob leaves a vent : (obj/machinery/atmospherics/unary/vent_exited)
+#define COMSIG_MOB_VENTCRAWL_END "ventcrawl_end"
+
+///called when a ventcrawling mob enters a vent : (mob/entering_mob)
+#define COMSIG_VENT_CRAWLER_ENTERED "ventcrawl_entered_vent"
+///called when a ventcrawling mob leaves a vent : (mob/exiting_mob)
+#define COMSIG_VENT_CRAWLER_EXITED "ventcrawl_exit_vent"
+
//NTnet
///called on an object by its NTNET connection component on receive. (sending_id(number), sending_netname(text), data(datum/netdata))
diff --git a/code/datums/ghost_spawn_options.dm b/code/datums/ghost_spawn_options.dm
index 3f1724aa57..31832e8f20 100644
--- a/code/datums/ghost_spawn_options.dm
+++ b/code/datums/ghost_spawn_options.dm
@@ -55,6 +55,8 @@
host.ckey = user.ckey
host.add_ventcrawl(vent_found)
to_chat(host, span_info("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent."))
+ SEND_SIGNAL(host,COMSIG_MOB_VENTCRAWL_START,vent_found)
+ SEND_SIGNAL(vent_found,COMSIG_VENT_CRAWLER_ENTERED,host)
/datum/tgui_module/ghost_spawn_menu/proc/become_drone(mob/observer/dead/user, fabricator)
if(SSticker.current_state < GAME_STATE_PLAYING)
diff --git a/code/modules/mob/living/simple_mob/life.dm b/code/modules/mob/living/simple_mob/life.dm
index 259fefc308..cd5a070de0 100644
--- a/code/modules/mob/living/simple_mob/life.dm
+++ b/code/modules/mob/living/simple_mob/life.dm
@@ -11,7 +11,6 @@
handle_weakened()
handle_paralysed()
handle_supernatural()
- handle_atmos()
handle_special()
@@ -102,73 +101,62 @@
/mob/living/simple_mob/proc/handle_special()
return
-
// Handle interacting with and taking damage from atmos
-// TODO - Refactor this to use handle_environment() like a good /mob/living
-/mob/living/simple_mob/proc/handle_atmos()
- var/atmos_unsuitable = 0
+/mob/living/simple_mob/handle_environment(datum/gas_mixture/environment)
if(in_stasis)
return 1 // return early to skip atmos checks
if(is_incorporeal())
return 1
- var/atom/A = src.loc
+ if( abs(environment.temperature - bodytemperature) > temperature_range )
+ bodytemperature += ((environment.temperature - bodytemperature) / 5)
- if(istype(A,/turf))
- var/turf/T = A
+ var/atmos_unsuitable = 0
+ if(min_oxy && environment.gas[GAS_O2] < min_oxy)
+ atmos_unsuitable = 1
+ throw_alert("oxy", /atom/movable/screen/alert/not_enough_oxy)
+ else if(max_oxy && environment.gas[GAS_O2] > max_oxy)
+ atmos_unsuitable = 1
+ throw_alert("oxy", /atom/movable/screen/alert/too_much_oxy)
+ else
+ clear_alert("oxy")
- var/datum/gas_mixture/Environment = T.return_air()
+ if(min_tox && environment.gas[GAS_PHORON] < min_tox)
+ atmos_unsuitable = 2
+ throw_alert("tox_in_air", /atom/movable/screen/alert/not_enough_tox)
+ else if(max_tox && environment.gas[GAS_PHORON] > max_tox)
+ atmos_unsuitable = 2
+ throw_alert("tox_in_air", /atom/movable/screen/alert/tox_in_air)
+ else
+ clear_alert("tox_in_air")
- if(Environment)
+ if(min_n2 && environment.gas[GAS_N2] < min_n2)
+ atmos_unsuitable = 1
+ throw_alert("n2o", /atom/movable/screen/alert/not_enough_nitro)
+ else if(max_n2 && environment.gas[GAS_N2] > max_n2)
+ atmos_unsuitable = 1
+ throw_alert("n2o", /atom/movable/screen/alert/too_much_nitro)
+ else
+ clear_alert("n2o")
- if( abs(Environment.temperature - bodytemperature) > temperature_range ) //VOREStation Edit: heating adjustments
- bodytemperature += ((Environment.temperature - bodytemperature) / 5)
+ if(min_co2 && environment.gas[GAS_CO2] < min_co2)
+ atmos_unsuitable = 1
+ throw_alert("co2", /atom/movable/screen/alert/not_enough_co2)
+ else if(max_co2 && environment.gas[GAS_CO2] > max_co2)
+ atmos_unsuitable = 1
+ throw_alert("co2", /atom/movable/screen/alert/too_much_co2)
+ else
+ clear_alert("co2")
- if(min_oxy && Environment.gas[GAS_O2] < min_oxy)
- atmos_unsuitable = 1
- throw_alert("oxy", /atom/movable/screen/alert/not_enough_oxy)
- else if(max_oxy && Environment.gas[GAS_O2] > max_oxy)
- atmos_unsuitable = 1
- throw_alert("oxy", /atom/movable/screen/alert/too_much_oxy)
- else
- clear_alert("oxy")
-
- if(min_tox && Environment.gas[GAS_PHORON] < min_tox)
- atmos_unsuitable = 2
- throw_alert("tox_in_air", /atom/movable/screen/alert/not_enough_tox)
- else if(max_tox && Environment.gas[GAS_PHORON] > max_tox)
- atmos_unsuitable = 2
- throw_alert("tox_in_air", /atom/movable/screen/alert/tox_in_air)
- else
- clear_alert("tox_in_air")
-
- if(min_n2 && Environment.gas[GAS_N2] < min_n2)
- atmos_unsuitable = 1
- throw_alert("n2o", /atom/movable/screen/alert/not_enough_nitro)
- else if(max_n2 && Environment.gas[GAS_N2] > max_n2)
- atmos_unsuitable = 1
- throw_alert("n2o", /atom/movable/screen/alert/too_much_nitro)
- else
- clear_alert("n2o")
-
- if(min_co2 && Environment.gas[GAS_CO2] < min_co2)
- atmos_unsuitable = 1
- throw_alert("co2", /atom/movable/screen/alert/not_enough_co2)
- else if(max_co2 && Environment.gas[GAS_CO2] > max_co2)
- atmos_unsuitable = 1
- throw_alert("co2", /atom/movable/screen/alert/too_much_co2)
- else
- clear_alert("co2")
-
- if(min_ch4 && Environment.gas[GAS_CH4] < min_ch4)
- atmos_unsuitable = 2
- throw_alert("methane_in_air", /atom/movable/screen/alert/not_enough_methane)
- else if(max_tox && Environment.gas[GAS_CH4] > max_ch4)
- atmos_unsuitable = 2
- throw_alert("methane_in_air", /atom/movable/screen/alert/methane_in_air)
- else
- clear_alert("methane_in_air")
+ if(min_ch4 && environment.gas[GAS_CH4] < min_ch4)
+ atmos_unsuitable = 2
+ throw_alert("methane_in_air", /atom/movable/screen/alert/not_enough_methane)
+ else if(max_tox && environment.gas[GAS_CH4] > max_ch4)
+ atmos_unsuitable = 2
+ throw_alert("methane_in_air", /atom/movable/screen/alert/methane_in_air)
+ else
+ clear_alert("methane_in_air")
//Atmos effect
if(bodytemperature < minbodytemp)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon.dm
index e4dac2f548..112126086a 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon.dm
@@ -71,14 +71,12 @@
/mob/living/simple_mob/vore/demon/UnarmedAttack()
if(shifted_out)
return FALSE
-
. = ..()
/mob/living/simple_mob/vore/demon/can_fall()
if(shifted_out)
return FALSE
-
- return ..()
+ . = ..()
/mob/living/simple_mob/vore/demon/zMove(direction)
if(shifted_out)
@@ -86,26 +84,23 @@
if(destination)
forceMove(destination)
return TRUE
-
- return ..()
+ . = ..()
/mob/living/simple_mob/vore/demon/Life()
. = ..()
if(shifted_out)
density = FALSE
-/mob/living/simple_mob/vore/demon/handle_atmos()
+/mob/living/simple_mob/vore/demon/handle_environment(datum/gas_mixture/environment) // TODO - Refactor demons to use is_incorporeal()
if(shifted_out)
return
- else
- return .=..()
+ . = ..()
/mob/living/simple_mob/vore/demon/update_canmove()
if(is_shifting)
canmove = FALSE
return canmove
- else
- return ..()
+ . = ..()
/mob/living/simple_mob/vore/demon/apply_melee_effects(var/atom/A)
if(isliving(A))
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demonAI.dm b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demonAI.dm
index c399d88a3f..3b3b67e4cb 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demonAI.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demonAI.dm
@@ -74,14 +74,12 @@
/mob/living/simple_mob/vore/demonAI/UnarmedAttack()
if(shifted_out)
return FALSE
-
. = ..()
/mob/living/simple_mob/vore/demonAI/can_fall()
if(shifted_out)
return FALSE
-
- return ..()
+ . = ..()
/mob/living/simple_mob/vore/demonAI/zMove(direction)
if(shifted_out)
@@ -89,19 +87,17 @@
if(destination)
forceMove(destination)
return TRUE
-
- return ..()
+ . = ..()
/mob/living/simple_mob/vore/demonAI/Life()
. = ..()
if(shifted_out)
density = FALSE
-/mob/living/simple_mob/vore/demonAI/handle_atmos()
+/mob/living/simple_mob/vore/demonAI/handle_environment(datum/gas_mixture/environment) // TODO - Refactor demons to use is_incorporeal()
if(shifted_out)
return
- else
- return .=..()
+ . = ..()
/mob/living/simple_mob/vore/demonAI/update_canmove()
if(is_shifting)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
index 29fa42e05b..0844f243f9 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
@@ -347,11 +347,6 @@
return TRUE
return FALSE
-/mob/living/simple_mob/shadekin/handle_atmos()
- if(comp.in_phase)
- return
- else
- return .=..()
/* //VOREStation AI Removal
//Friendly ones wander towards people, maybe shy-ly if they are set to shy
/mob/living/simple_mob/shadekin/handle_wander_movement()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index ff5cf908b0..97a406720b 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -262,5 +262,6 @@
VAR_PROTECTED/list/resistances
var/custom_footstep = FOOTSTEP_MOB_SHOE
+ var/vent_crawl_time = 4.5 SECONDS // Time to animate entering a vent
VAR_PRIVATE/is_motion_tracking = FALSE // Prevent multiple unsubs and resubs, also used to check if the vis layer is enabled, use has_motiontracking() to get externally.
VAR_PRIVATE/wants_to_see_motion_echos = TRUE
diff --git a/code/modules/ventcrawl/ventcrawl.dm b/code/modules/ventcrawl/ventcrawl.dm
index f9dd5444e7..bef1b7f4af 100644
--- a/code/modules/ventcrawl/ventcrawl.dm
+++ b/code/modules/ventcrawl/ventcrawl.dm
@@ -21,8 +21,8 @@ var/list/ventcrawl_machinery = list(
/mob/living/var/list/icon/pipes_shown = list()
/mob/living/var/last_played_vent
-/mob/living/var/is_ventcrawling = 0
-/mob/living/var/prepping_to_ventcrawl = 0
+/mob/living/var/is_ventcrawling = FALSE
+/mob/living/var/prepping_to_ventcrawl = FALSE
/mob/var/next_play_vent = 0
/mob/living/proc/can_ventcrawl()
@@ -180,8 +180,12 @@ var/list/ventcrawl_machinery = list(
break
if(vent_found)
- if(vent_found.network && (vent_found.network.normal_members.len || vent_found.network.line_members.len))
+ if(SEND_SIGNAL(src,COMSIG_MOB_VENTCRAWL_CHECK,vent_found) & VENT_CRAWL_BLOCK_ENTRY)
+ return
+ if(SEND_SIGNAL(vent_found,COMSIG_VENT_CRAWLER_CHECK,src) & VENT_CRAWL_BLOCK_ENTRY)
+ return
+ if(vent_found.network && (vent_found.network.normal_members.len || vent_found.network.line_members.len))
to_chat(src, "You begin climbing into the ventilation system...")
if(vent_found.air_contents && !issilicon(src))
@@ -202,15 +206,20 @@ var/list/ventcrawl_machinery = list(
to_chat(src, span_warning("You feel a strong drag pulling you into the vent."))
if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE)
to_chat(src, span_warning("You feel a strong current pushing you away from the vent."))
- if(HAZARD_HIGH_PRESSURE to INFINITY)
+ if(HAZARD_HIGH_PRESSURE to (HAZARD_HIGH_PRESSURE*2))
to_chat(src, span_danger("You feel a roaring wind pushing you away from the vent!"))
+ if((HAZARD_HIGH_PRESSURE*2) to INFINITY) // A little too crazy to enter
+ to_chat(src, span_danger("You're pushed away by the extreme pressure in the vent!"))
+ return
- fade_towards(vent_found,45)
- prepping_to_ventcrawl = 1
- spawn(50)
- prepping_to_ventcrawl = 0
- if(!do_after(src, 45, target = src))
+ // Handle animation delay
+ fade_towards(vent_found, vent_crawl_time)
+ prepping_to_ventcrawl = TRUE
+ if(!do_after(src, vent_crawl_time, target = src))
+ prepping_to_ventcrawl = FALSE
return
+ prepping_to_ventcrawl = FALSE
+
if(!can_ventcrawl())
return
@@ -218,7 +227,8 @@ var/list/ventcrawl_machinery = list(
forceMove(vent_found)
add_ventcrawl(vent_found)
-
+ SEND_SIGNAL(src,COMSIG_MOB_VENTCRAWL_START,vent_found)
+ SEND_SIGNAL(vent_found,COMSIG_VENT_CRAWLER_ENTERED,src)
else
to_chat(src, "This vent is not connected to anything.")
@@ -226,7 +236,7 @@ var/list/ventcrawl_machinery = list(
to_chat(src, "You must be standing on or beside an air vent to enter it.")
/mob/living/proc/add_ventcrawl(obj/machinery/atmospherics/starting_machine)
- is_ventcrawling = 1
+ is_ventcrawling = TRUE
//candrop = 0
var/datum/pipe_network/network = starting_machine.return_network(starting_machine)
if(!network)
@@ -242,7 +252,7 @@ var/list/ventcrawl_machinery = list(
client.screen += GLOB.global_hud.centermarker
/mob/living/proc/remove_ventcrawl()
- is_ventcrawling = 0
+ is_ventcrawling = FALSE
//candrop = 1
if(client)
for(var/image/current_image in pipes_shown)
diff --git a/code/modules/ventcrawl/ventcrawl_atmospherics.dm b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
index 710f296d88..8b5c11c386 100644
--- a/code/modules/ventcrawl/ventcrawl_atmospherics.dm
+++ b/code/modules/ventcrawl/ventcrawl_atmospherics.dm
@@ -4,6 +4,8 @@
for(var/mob/living/M in src) //ventcrawling is serious business
M.remove_ventcrawl()
M.forceMove(get_turf(src))
+ SEND_SIGNAL(M,COMSIG_MOB_VENTCRAWL_END,src)
+ SEND_SIGNAL(src,COMSIG_VENT_CRAWLER_EXITED,M)
if(pipe_image)
for(var/mob/living/M in GLOB.player_list)
if(M.client)
@@ -43,6 +45,7 @@
if(world.time > user.next_play_vent)
user.next_play_vent = world.time+30
var/turf/T = get_turf(src)
+ SSmotiontracker.ping(T,40) // Teshari rattler
playsound(T, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
var/message = pick(
prob(90);"* clunk *",
@@ -56,6 +59,8 @@
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)
+ SEND_SIGNAL(user,COMSIG_MOB_VENTCRAWL_END,src)
+ SEND_SIGNAL(src,COMSIG_VENT_CRAWLER_EXITED,user)
user.visible_message("You hear something squeezing through the pipes.", "You climb out the ventilation system.")
user.canmove = 0
spawn(1)
diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm
index f135846d0c..95b43d6478 100644
--- a/code/modules/vore/eating/belly_obj_vr.dm
+++ b/code/modules/vore/eating/belly_obj_vr.dm
@@ -883,6 +883,11 @@
if (!(M in contents))
return 0 // They weren't in this belly anyway
+ // Ventcrawlings will explode their vent to avoid exploits
+ if(istype(owner.loc,/obj/machinery/atmospherics))
+ var/obj/machinery/atmospherics/our_pipe = owner.loc
+ our_pipe.blowout(owner)
+
if(istype(M, /mob/living/simple_mob/vore/morph/dominated_prey))
var/mob/living/simple_mob/vore/morph/dominated_prey/p = M
p.undo_prey_takeover(FALSE)
diff --git a/icons/atmos/vent_scrubber.dmi b/icons/atmos/vent_scrubber.dmi
index ddf9b159b7b43a7e9cdd3a312dc64c04e371fd5c..eb956f373501e6bd1a7060172a072ed6f5898a1c 100644
GIT binary patch
literal 8207
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
GoA_VnD5j$T
literal 8106
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
AW&i*H