diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm
index 3892de00acdf..fea25a714d73 100644
--- a/code/datums/components/crafting/recipes.dm
+++ b/code/datums/components/crafting/recipes.dm
@@ -248,6 +248,16 @@
time = 4 SECONDS
category = CAT_ROBOT
+/datum/crafting_recipe/Atmosbot
+ name = "Automatic Station Stabilizer Bot"
+ result = /mob/living/simple_animal/bot/atmosbot
+ reqs = list(/obj/item/analyzer = 1,
+ /obj/item/bodypart/r_arm/robot = 1,
+ /obj/item/assembly/prox_sensor = 1,
+ /obj/item/grenade/chem_grenade/smart_metal_foam = 1)
+ time = 6 SECONDS
+ category = CAT_ROBOT
+
/datum/crafting_recipe/improvised_pneumatic_cannon //Pretty easy to obtain but
name = "Pneumatic Cannon"
result = /obj/item/pneumatic_cannon/ghetto
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 6bd9e304cd8b..12315ff83940 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -476,3 +476,10 @@
/obj/effect/temp_visual/dir_setting/space_wind/Initialize(mapload, set_dir, set_alpha = 255)
. = ..()
alpha = set_alpha
+
+/obj/effect/temp_visual/vent_wind
+ icon = 'icons/effects/atmospherics.dmi'
+ icon_state = "vent_wind"
+ layer = FLY_LAYER
+ duration = 0.48 SECONDS
+ mouse_opacity = 0
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 1c19a96cd32b..160f19d9606e 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -478,6 +478,15 @@ GENE SCANNER
user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
return BRUTELOSS
+/obj/item/analyzer/attackby(obj/O, mob/living/user)
+ if(istype(O, /obj/item/bodypart/l_arm/robot) || istype(O, /obj/item/bodypart/r_arm/robot))
+ to_chat(user, "You add [O] to [src].")
+ qdel(O)
+ qdel(src)
+ user.put_in_hands(new /obj/item/bot_assembly/atmosbot)
+ else
+ ..()
+
/obj/item/analyzer/attack_self(mob/user)
add_fingerprint(user)
scangasses(user) //yogs start: Makes the gas scanning able to be used elseware
diff --git a/code/modules/mob/living/simple_animal/bot/atmosbot.dm b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
new file mode 100644
index 000000000000..868674d90709
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
@@ -0,0 +1,219 @@
+#define ATMOSBOT_MAX_AREA_SCAN 100
+#define ATMOSBOT_HOLOBARRIER_COOLDOWN 150
+
+#define ATMOSBOT_CHECK_BREACH 0
+#define ATMOSBOT_LOW_OXYGEN 1
+#define ATMOSBOT_AREA_STABLE 4
+
+#define ATMOSBOT_NOTHING 0
+#define ATMOSBOT_DEPLOY_FOAM 1
+#define ATMOSBOT_SPRAY_MIASMA 5
+
+//Floorbot
+/mob/living/simple_animal/bot/atmosbot
+ name = "\improper Automatic Station Stabilizer Bot"
+ desc = "Or the A.S.S. Bot for short."
+ icon = 'icons/mob/aibots.dmi'
+ icon_state = "atmosbot0"
+ density = FALSE
+ anchored = FALSE
+ health = 25
+ maxHealth = 25
+ spacewalk = TRUE
+
+ radio_key = /obj/item/encryptionkey/headset_eng
+ radio_channel = RADIO_CHANNEL_ENGINEERING
+ bot_type = FLOOR_BOT
+ model = "Floorbot"
+ bot_core = /obj/machinery/bot_core/floorbot
+ window_id = "autofloor"
+ window_name = "Automatic Station Atmospherics Restabilizer v1.1"
+ path_image_color = "#FFA500"
+
+ auto_patrol = TRUE
+
+ var/action
+ var/turf/target
+ //The pressure at which the bot scans for breaches
+ var/breached_pressure = 20
+ //Weakref of deployed barrier
+ var/datum/weakref/deployed_smartmetal
+ //Deployment time of last barrier
+ var/last_barrier_tick
+ //Gasses
+
+
+/mob/living/simple_animal/bot/atmosbot/Initialize(mapload, new_toolbox_color)
+ . = ..()
+ var/datum/job/engineer/J = new/datum/job/engineer
+ access_card.access += J.get_access()
+ prev_access = access_card.access
+
+/mob/living/simple_animal/bot/atmosbot/turn_on()
+ . = ..()
+ update_icon()
+
+/mob/living/simple_animal/bot/atmosbot/turn_off()
+ . = ..()
+ update_icon()
+
+/mob/living/simple_animal/bot/atmosbot/set_custom_texts()
+ text_hack = "You corrupt [name]'s safety protocols."
+ text_dehack = "You detect errors in [name] and reset his programming."
+ text_dehack_fail = "[name] is not responding to reset commands!"
+
+/mob/living/simple_animal/bot/atmosbot/emag_act(mob/user)
+ . = ..()
+ if(emagged == 2)
+ audible_message("[src] ominously whirs....")
+ playsound(src, "sparks", 75, TRUE)
+
+/mob/living/simple_animal/bot/atmosbot/handle_automated_action()
+ if(!..())
+ return
+
+ if(prob(5))
+ audible_message("[src] makes an excited whirring sound!")
+
+ action = ATMOSBOT_NOTHING
+ if(!isspaceturf(get_turf(src)))
+ switch(check_area_atmos())
+ if(ATMOSBOT_CHECK_BREACH)
+ if(last_barrier_tick + ATMOSBOT_HOLOBARRIER_COOLDOWN < world.time)
+ target = return_nearest_breach()
+ action = ATMOSBOT_DEPLOY_FOAM
+ update_icon()
+
+ if(!target)
+ if(auto_patrol)
+ if(mode == BOT_IDLE || mode == BOT_START_PATROL)
+ start_patrol()
+
+ if(mode == BOT_PATROL)
+ bot_patrol()
+
+ if(target)
+ if(loc == get_turf(target))
+ if(check_bot(target))
+ if(prob(50))
+ target = null
+ path = list()
+ return
+ //Do foam here
+ deploy_smartmetal()
+
+
+ if(!LAZYLEN(path))
+ var/turf/target_turf = get_turf(target)
+ path = get_path_to(src, target_turf, /turf/proc/Distance_cardinal, 0, 30, id=access_card, simulated_only = FALSE)
+
+ if(!bot_move(target))
+ add_to_ignore(target)
+ target = null
+ mode = BOT_IDLE
+ return
+
+ else if(!bot_move(target))
+ target = null
+ mode = BOT_IDLE
+ return
+
+
+/mob/living/simple_animal/bot/atmosbot/proc/deploy_smartmetal()
+ if(emagged == 2)
+ explosion(src.loc,1,2,4,flame_range = 2)
+ qdel(src)
+ else
+ deployed_smartmetal = WEAKREF(new /obj/effect/particle_effect/foam/metal/smart(get_turf(src)))
+ qdel(src)
+ return
+
+//Analyse the atmosphere to see if there is a potential breach nearby
+/mob/living/simple_animal/bot/atmosbot/proc/check_area_atmos()
+ var/turf/T = get_turf(src)
+ var/datum/gas_mixture/gas_mix = T.return_air()
+ if(gas_mix.return_pressure() < breached_pressure)
+ return ATMOSBOT_CHECK_BREACH
+ //Too little oxygen or too little pressure
+ var/partial_pressure = R_IDEAL_GAS_EQUATION * gas_mix.return_temperature() / gas_mix.return_volume()
+ var/oxygen_moles = gas_mix.get_moles(/datum/gas/oxygen) * partial_pressure
+ if(oxygen_moles < 20 || gas_mix.return_pressure() < WARNING_LOW_PRESSURE)
+ return ATMOSBOT_LOW_OXYGEN
+
+//Returns the closest turf that needs a holoprojection set up
+/mob/living/simple_animal/bot/atmosbot/proc/return_nearest_breach()
+ var/turf/origin = get_turf(src)
+
+ if(origin.blocks_air)
+ return null
+
+ var/room_limit = ATMOSBOT_MAX_AREA_SCAN
+ var/list/checked_turfs = list()
+ var/list/to_check_turfs = list(origin)
+ while(room_limit > 0 && LAZYLEN(to_check_turfs))
+ room_limit --
+ var/turf/checking_turf = to_check_turfs[1]
+ //We have checked this turf
+ checked_turfs += checking_turf
+ to_check_turfs -= checking_turf
+ var/blocked = FALSE
+ for(var/obj/structure/holosign/barrier/atmos/A in checking_turf)
+ blocked = TRUE
+ break
+ if(blocked || !checking_turf.CanAtmosPass(checking_turf))
+ continue
+ //Add adjacent turfs
+ for(var/direction in list(NORTH, SOUTH, EAST, WEST))
+ var/turf/adjacent_turf = get_step(checking_turf, direction)
+ if(adjacent_turf in checked_turfs || !adjacent_turf.CanAtmosPass(adjacent_turf) || istype(adjacent_turf.loc, /area/space))
+ continue
+ if(isspaceturf(adjacent_turf))
+ return checking_turf
+ to_check_turfs |= adjacent_turf
+ return null
+
+/mob/living/simple_animal/bot/atmosbot/get_controls(mob/user)
+ var/dat
+ dat += hack(user)
+ dat += showpai(user)
+ dat += "Atmospheric Stabalizer Controls v1.1
"
+ dat += "Status: [on ? "On" : "Off"]
"
+ dat += "Maintenance panel panel is [open ? "opened" : "closed"]
"
+ if(!locked || issilicon(user) || IsAdminGhost(user))
+ dat += "Breach Pressure: [breached_pressure]
"
+ dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
"
+ return dat
+
+/mob/living/simple_animal/bot/atmosbot/Topic(href, href_list)
+ if(..())
+ return TRUE
+
+ if(href_list["set_breach_pressure"])
+ var/new_breach_pressure = input(usr, "Pressure to scan for breaches at? (0 to 100)", "Breach Pressure") as num
+ if(!isnum(new_breach_pressure) || new_breach_pressure < 0 || new_breach_pressure > 100)
+ return
+ breached_pressure = new_breach_pressure
+ update_controls()
+ update_icon()
+
+/mob/living/simple_animal/bot/atmosbot/update_icon()
+ icon_state = "atmosbot[on][on?"_[action]":""]"
+
+/mob/living/simple_animal/bot/atmosbot/UnarmedAttack(atom/A, proximity)
+ if(isturf(A) && A == get_turf(src))
+ return deploy_smartmetal()
+ return ..()
+
+/mob/living/simple_animal/bot/atmosbot/explode()
+ on = FALSE
+ visible_message("[src] blows apart!")
+
+ var/atom/Tsec = drop_location()
+
+ new /obj/item/assembly/prox_sensor(Tsec)
+ new /obj/item/analyzer(Tsec)
+ if(prob(50))
+ drop_part(robot_arm, Tsec)
+
+ do_sparks(3, TRUE, src)
+ ..()
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index 6e86183706c9..156d04178592 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -500,3 +500,33 @@
F.name = created_name
qdel(I)
qdel(src)
+
+//Atmosbot Assembly
+/obj/item/bot_assembly/atmosbot
+ name = "incomplete atmosbot assembly"
+ desc = "An incomplete atmosbot with an analyser attached to it"
+ icon_state = "atmosbot_assembly"
+ created_name = "Atmosbot"
+
+/obj/item/bot_assembly/atmosbot/attackby(obj/item/I, mob/user, params)
+ ..()
+ switch(build_step)
+ if(ASSEMBLY_FIRST_STEP)
+ if(istype(I, /obj/item/grenade/chem_grenade/smart_metal_foam))
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ return
+ to_chat(user,"You add the [I] to [src]!")
+ icon_state = "atmosbot_assembly_tank"
+ desc = "An incomplete atmosbot assembly with a tank strapped to it."
+ qdel(I)
+ build_step++
+
+ if(ASSEMBLY_SECOND_STEP)
+ if(isprox(I))
+ if(!can_finish_build(I, user))
+ return
+ to_chat(user, "You add the [I] to [src]! Beep Boop!")
+ var/mob/living/simple_animal/bot/atmosbot/A = new(drop_location())
+ A.name = created_name
+ qdel(I)
+ qdel(src)
diff --git a/icons/effects/atmospherics.dmi b/icons/effects/atmospherics.dmi
index 5f6e056470db..77721a81ada8 100644
Binary files a/icons/effects/atmospherics.dmi and b/icons/effects/atmospherics.dmi differ
diff --git a/icons/mob/aibots.dmi b/icons/mob/aibots.dmi
index 844791a02a74..14054ebe40ce 100644
Binary files a/icons/mob/aibots.dmi and b/icons/mob/aibots.dmi differ
diff --git a/yogstation.dme b/yogstation.dme
index 4a40273aea56..1c4ea6713e37 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -2326,6 +2326,7 @@
#include "code\modules\mob\living\simple_animal\shade.dm"
#include "code\modules\mob\living\simple_animal\simple_animal.dm"
#include "code\modules\mob\living\simple_animal\status_procs.dm"
+#include "code\modules\mob\living\simple_animal\bot\atmosbot.dm"
#include "code\modules\mob\living\simple_animal\bot\bot.dm"
#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm"
#include "code\modules\mob\living\simple_animal\bot\construction.dm"