diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index d583084791..3454b1ea7f 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -44,6 +44,8 @@ avoid code duplication. This includes items that may sometimes act as a standard
/mob/living/attackby(obj/item/I, mob/user)
if(!ismob(user))
return 0
+ if(src.Airattackby(I,user))
+ return 0
if(can_operate(src) && I.do_surgery(src,user)) //Surgery
return 1
if(attempt_vr(src,"vore_attackby",args)) return //VOREStation Code
diff --git a/code/modules/mob/airhack.dm b/code/modules/mob/airhack.dm
new file mode 100644
index 0000000000..9b815dabec
--- /dev/null
+++ b/code/modules/mob/airhack.dm
@@ -0,0 +1,113 @@
+/mob
+ var/datum/gas_mixture/air_contents = new
+
+ var/obj/machinery/atmospherics/portables_connector/connected_port
+ var/obj/item/weapon/tank/holding
+
+ var/volume = 1000
+ var/destroyed = 0
+
+ var/start_pressure = ONE_ATMOSPHERE
+ var/maximum_pressure = 90 * ONE_ATMOSPHERE
+/mob/New()
+ ..()
+
+ air_contents.volume = volume
+ air_contents.temperature = T20C
+
+ return 1
+/mob/Destroy()
+ qdel_null(air_contents)
+ qdel_null(holding)
+ . = ..()
+/mob/Life()
+ if(!connected_port) //only react when pipe_network will ont it do it for you
+ //Allow for reactions
+ air_contents.react()
+ . = ..()
+/mob/proc/StandardAirMix()
+ return list(
+ "oxygen" = O2STANDARD * MolesForPressure(),
+ "nitrogen" = N2STANDARD * MolesForPressure())
+/mob/proc/MolesForPressure(var/target_pressure = start_pressure)
+ return (target_pressure * air_contents.volume) / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
+
+/mob/proc/connect(obj/machinery/atmospherics/portables_connector/new_port)
+ //Make sure not already connected to something else
+ if(connected_port || !new_port || new_port.connected_device)
+ return 0
+
+ //Make sure are close enough for a valid connection
+ if(new_port.loc != loc)
+ return 0
+
+ //Perform the connection
+ connected_port = new_port
+ connected_port.connected_device = src
+ connected_port.on = 1 //Activate port updates
+
+ anchored = 1 //Prevent movement
+
+ //Actually enforce the air sharing
+ var/datum/pipe_network/network = connected_port.return_network(src)
+ if(network && !network.gases.Find(air_contents))
+ network.gases += air_contents
+ network.update = 1
+
+ return 1
+
+/mob/proc/disconnect()
+ if(!connected_port)
+ return 0
+
+ var/datum/pipe_network/network = connected_port.return_network(src)
+ if(network)
+ network.gases -= air_contents
+
+ anchored = 0
+
+ connected_port.connected_device = null
+ connected_port = null
+
+ return 1
+
+/mob/proc/update_connected_network()
+ if(!connected_port)
+ return
+
+ var/datum/pipe_network/network = connected_port.return_network(src)
+ if (network)
+ network.update = 1
+
+/mob/proc/Airattackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
+ if (istype(W, /obj/item/weapon/wrench))
+ if(connected_port)
+ disconnect()
+ user << "You disconnect \the [src] from the port."
+ update_icon()
+ playsound(src, W.usesound, 50, 1)
+ return 1
+ else
+ var/obj/machinery/atmospherics/portables_connector/possible_port = locate(/obj/machinery/atmospherics/portables_connector/) in loc
+ if(possible_port)
+ if(connect(possible_port))
+ user << "You connect \the [src] to the port."
+ update_icon()
+ playsound(src, W.usesound, 50, 1)
+ return
+ else
+ user << "\The [src] failed to connect to the port."
+ return
+ else
+ user << "Nothing happens."
+ return 1
+
+ else if ((istype(W, /obj/item/device/analyzer)) && Adjacent(user))
+ var/obj/item/device/analyzer/A = W
+ A.analyze_gases(src, user)
+ return 1
+ return 0
+
+/mob/living/hit_with_weapon(obj/item/I, mob/living/user, var/effective_force, var/hit_zone)
+ user.Airattackby(I,src)
+ ..(I,user,effective_force,hit_zone)
\ No newline at end of file