diff --git a/baystation12.dme b/baystation12.dme
index 3285a6fcb61..a4dce912325 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -754,6 +754,7 @@
#include "code\modules\clothing\shoes\magboots.dm"
#include "code\modules\clothing\shoes\miscellaneous.dm"
#include "code\modules\clothing\spacesuits\alien.dm"
+#include "code\modules\clothing\spacesuits\breaches.dm"
#include "code\modules\clothing\spacesuits\captain.dm"
#include "code\modules\clothing\spacesuits\ert.dm"
#include "code\modules\clothing\spacesuits\miscellaneous.dm"
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 1d8fd6a36b7..069994dbf85 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -567,4 +567,642 @@
return
-//////////////////////////////REMINDER: Make it lock once you place some fucker inside.
\ No newline at end of file
+//////////////////////////////REMINDER: Make it lock once you place some fucker inside.
+
+//God this entire file is fucking awful
+//Suit painter for Bay's special snowflake aliums.
+
+/obj/machinery/suit_cycler
+
+ name = "suit cycler"
+ desc = "An industrial machine for repairing, painting and equipping hardsuits."
+ anchored = 1
+ density = 1
+
+ icon = 'icons/obj/suitstorage.dmi'
+ icon_state = "suitstorage000000100"
+
+ req_access = list(access_captain,access_heads)
+
+ var/active = 0 // PLEASE HOLD.
+ var/safeties = 1 // The cycler won't start with a living thing inside it unless safeties are off.
+ var/irradiating = 0 // If this is > 0, the cycler is decontaminating whatever is inside it.
+ var/radiation_level = 2 // 1 is removing germs, 2 is removing blood, 3 is removing phoron.
+ var/model_text = "" // Some flavour text for the topic box.
+ var/locked = 1 // If locked, nothing can be taken from or added to the cycler.
+ var/panel_open = 0 // Hacking!
+
+ // Wiring bollocks.
+ var/wires = 15
+ var/electrified = 0
+ var/const/WIRE_EXTEND = 1 // Safeties
+ var/const/WIRE_SCANID = 2 // Locked status
+ var/const/WIRE_SHOCK = 3 // What it says on the tin.
+
+ //Departments that the cycler can paint suits to look like.
+ var/list/departments = list("Engineering","Mining","Medical","Security","Atmos")
+ //Species that the suits can be configured to fit.
+ var/list/species = list("Human","Skrell","Unathi","Tajaran")
+
+ var/target_department = "Engineering"
+ var/target_species = "Human"
+
+ var/mob/living/carbon/human/occupant = null
+ var/obj/item/clothing/suit/space/rig/suit = null
+ var/obj/item/clothing/head/helmet/space/helmet = null
+
+/obj/machinery/suit_cycler/engineering
+ name = "Engineering suit cycler"
+ model_text = "Engineering"
+ req_access = list(access_construction)
+ departments = list("Engineering","Atmos")
+ species = list("Human","Unathi","Tajaran")
+
+/obj/machinery/suit_cycler/mining
+ name = "Mining suit cycler"
+ model_text = "Mining"
+ req_access = list(access_mining)
+ departments = list("Mining")
+ species = list("Human","Unathi","Tajaran")
+
+/obj/machinery/suit_cycler/attack_ai(mob/user as mob)
+ return src.attack_hand(user)
+
+/obj/machinery/suit_cycler/attack_paw(mob/user as mob)
+ user << "\blue The console controls are far too complicated for your tiny brain!"
+ return
+
+/obj/machinery/suit_cycler/attackby(obj/item/I as obj, mob/user as mob)
+
+ if(electrified != 0)
+ if(src.shock(user, 100))
+ return
+
+ //Hacking init.
+ if(istype(I, /obj/item/device/multitool) || istype(I, /obj/item/weapon/wirecutters))
+ if(panel_open)
+ attack_hand(user)
+ return
+ //Other interface stuff.
+ if(istype(I, /obj/item/weapon/grab))
+ var/obj/item/weapon/grab/G = I
+
+ if(!(ismob(G.affecting)))
+ return
+
+ if(locked)
+ user << "\red The suit cycler is locked."
+ return
+
+ if(src.contents.len > 0)
+ user << "\red There is no room inside the cycler for [G.affecting.name]."
+ return
+
+ visible_message("[user] starts putting [G.affecting.name] into the suit cycler.", 3)
+
+ if(do_after(user, 20))
+ if(!G || !G.affecting) return
+ var/mob/M = G.affecting
+ if (M.client)
+ M.client.perspective = EYE_PERSPECTIVE
+ M.client.eye = src
+ M.loc = src
+ src.occupant = M
+
+ src.add_fingerprint(user)
+ del(G)
+
+ src.updateUsrDialog()
+
+ return
+ else if(istype(I,/obj/item/weapon/screwdriver))
+
+ panel_open = !panel_open
+ user << "You [panel_open ? "open" : "close"] the maintenance panel."
+ src.updateUsrDialog()
+ return
+
+ else if(istype(I,/obj/item/weapon/card/emag))
+
+ if(emagged)
+ user << "\red The cycler has already been subverted."
+ return
+
+ var/obj/item/weapon/card/emag/E = I
+ src.updateUsrDialog()
+ E.uses--
+
+ //Clear the access reqs, disable the safeties, and open up all paintjobs.
+ user << "\red You run the sequencer across the interface, corrupting the operating protocols."
+ departments = list("Engineering","Mining","Medical","Security","Atmos","^%###^%$")
+ emagged = 1
+ safeties = 0
+ req_access = list()
+ return
+
+ else if(istype(I,/obj/item/clothing/head/helmet/space))
+
+ if(locked)
+ user << "\red The suit cycler is locked."
+ return
+
+ if(helmet)
+ user << "The cycler already contains a helmet."
+ return
+
+ user << "You fit \the [I] into the suit cycler."
+ user.drop_item()
+ I.loc = src
+ helmet = I
+
+ src.update_icon()
+ src.updateUsrDialog()
+ return
+
+ else if(istype(I,/obj/item/clothing/suit/space/rig))
+
+ if(locked)
+ user << "\red The suit cycler is locked."
+ return
+
+ if(suit)
+ user << "The cycler already contains a hardsuit."
+ return
+
+ var/obj/item/clothing/suit/space/rig/S = I
+
+ if(S.helmet)
+ user << "\The [S] will not fit into the cycler with a helmet attached."
+ return
+
+ if(S.boots)
+ user << "\The [S] will not fit into the cycler with boots attached."
+ return
+
+ user << "You fit \the [I] into the suit cycler."
+ user.drop_item()
+ I.loc = src
+ suit = I
+
+ src.update_icon()
+ src.updateUsrDialog()
+ return
+
+ ..()
+
+/obj/machinery/suit_cycler/attack_hand(mob/user as mob)
+
+ add_fingerprint(user)
+
+ if(..() || stat & (BROKEN|NOPOWER))
+ return
+
+ if(electrified != 0)
+ if(src.shock(user, 100))
+ return
+
+ usr.set_machine(src)
+
+ var/dat = "
Suit Cycler Interface"
+
+ if(src.active)
+ dat+= "
The [model_text ? "[model_text] " : ""]suit cycler is currently in use. Please wait..."
+
+ else if(locked)
+ dat += "
The [model_text ? "[model_text] " : ""]suit cycler is currently locked. Please contact your system administrator."
+ if(src.allowed(usr))
+ dat += "
\[unlock unit\]"
+ else
+ dat += "Suit cycler
"
+ dat += "Welcome to the [model_text ? "[model_text] " : ""]suit cycler control panel. \[lock unit\]
"
+
+ dat += "Maintenance
"
+ dat += "Helmet: [helmet ? "\the [helmet]" : "no helmet stored" ]. \[eject\]
"
+ dat += "Suit: [suit ? "\the [suit]" : "no suit stored" ]. \[eject\]"
+
+ if(suit && istype(suit))
+ dat += "[(suit.damage ? " \[repair\]" : "")]"
+
+ dat += "
UV decontamination systems: SYSTEM ERROR" : "green'>READY"]
"
+ dat += "Output level: [radiation_level]
"
+ dat += "\[select power level\] \[begin decontamination cycle\]
"
+
+ dat += "Customisation
"
+ dat += "Target product: [target_department], [target_species]."
+ dat += "
\[apply customisation routine\]
"
+
+ if(panel_open)
+ var/list/vendwires = list(
+ "Violet" = 1,
+ "Orange" = 2,
+ "Goldenrod" = 3,
+ )
+ dat += "Access Panel
"
+ for(var/wiredesc in vendwires)
+ var/is_uncut = src.wires & APCWireColorToFlag[vendwires[wiredesc]]
+ dat += "[wiredesc] wire: "
+ if(!is_uncut)
+ dat += "Mend"
+ else
+ dat += "Cut "
+ dat += "Pulse "
+ dat += "
"
+
+ dat += "
"
+ dat += "The orange light is [(electrified == 0) ? "off" : "on"].
"
+ dat += "The red light is [safeties ? "blinking" : "off"].
"
+ dat += "The yellow light is [locked ? "on" : "off"].
"
+
+ user << browse(dat, "window=suit_cycler")
+ onclose(user, "suit_cycler")
+ return
+
+/obj/machinery/suit_cycler/Topic(href, href_list)
+ if(href_list["eject_suit"])
+ if(!suit) return
+ suit.loc = get_turf(src)
+ suit = null
+ else if(href_list["eject_helmet"])
+ if(!helmet) return
+ helmet.loc = get_turf(src)
+ helmet = null
+ else if(href_list["select_department"])
+ target_department = input("Please select the target department paintjob.","Suit cycler",null) as null|anything in departments
+ else if(href_list["select_species"])
+ target_species = input("Please select the target species configuration.","Suit cycler",null) as null|anything in species
+ else if(href_list["select_rad_level"])
+ var/choices = list(1,2,3)
+ if(emagged)
+ choices = list(1,2,3,4,5)
+ radiation_level = input("Please select the desired radiation level.","Suit cycler",null) as null|anything in choices
+ else if(href_list["repair_suit"])
+
+ if(!suit) return
+ active = 1
+ spawn(100)
+ repair_suit()
+ finished_job()
+
+ else if(href_list["apply_paintjob"])
+
+ if(!suit && !helmet) return
+ active = 1
+ spawn(100)
+ apply_paintjob()
+ finished_job()
+
+ else if(href_list["toggle_safties"])
+ safeties = !safeties
+
+ else if(href_list["toggle_lock"])
+
+ if(src.allowed(usr))
+ locked = !locked
+ usr << "You [locked ? "" : "un"]lock \the [src]."
+ else
+ usr << "\red Access denied."
+
+ else if(href_list["begin_decontamination"])
+
+ if(safeties && occupant)
+ usr << "\red The cycler has detected an occupant. Please remove the occupant before commencing the decontamination cycle."
+ return
+
+ active = 1
+ irradiating = 10
+ src.updateUsrDialog()
+
+ sleep(10)
+
+ if(helmet)
+ if(radiation_level > 2)
+ helmet.decontaminate()
+ if(radiation_level > 1)
+ helmet.clean_blood()
+
+ if(suit)
+ if(radiation_level > 2)
+ suit.decontaminate()
+ if(radiation_level > 1)
+ suit.clean_blood()
+
+ else if ((href_list["cutwire"]) && (src.panel_open))
+ var/twire = text2num(href_list["cutwire"])
+ if (!( istype(usr.get_active_hand(), /obj/item/weapon/wirecutters) ))
+ usr << "You need wirecutters!"
+ return
+ if (src.isWireColorCut(twire))
+ src.mend(twire)
+ else
+ src.cut(twire)
+
+ else if ((href_list["pulsewire"]) && (src.panel_open))
+ var/twire = text2num(href_list["pulsewire"])
+ if (!istype(usr.get_active_hand(), /obj/item/device/multitool))
+ usr << "You need a multitool!"
+ return
+ if (src.isWireColorCut(twire))
+ usr << "You can't pulse a cut wire."
+ return
+ else
+ src.pulse(twire)
+
+ src.updateUsrDialog()
+ return
+
+/obj/machinery/suit_cycler/process()
+
+ if(electrified > 0)
+ electrified--
+
+ if(!active)
+ return
+
+ if(active && stat & (BROKEN|NOPOWER))
+ active = 0
+ irradiating = 0
+ electrified = 0
+ return
+
+ if(irradiating == 1)
+ finished_job()
+ irradiating = 0
+ return
+
+ irradiating--
+
+ if(occupant)
+ if(prob(radiation_level*2)) occupant.emote("scream")
+ if(radiation_level > 2)
+ occupant.take_organ_damage(0,radiation_level*2 + rand(1,3))
+ if(radiation_level > 1)
+ occupant.take_organ_damage(0,radiation_level + rand(1,3))
+ occupant.radiation += radiation_level*10
+
+/obj/machinery/suit_cycler/proc/finished_job()
+ var/turf/T = get_turf(src)
+ T.visible_message("\The [src] pings loudly.")
+ icon_state = initial(icon_state)
+ active = 0
+ src.updateUsrDialog()
+
+/obj/machinery/suit_cycler/proc/repair_suit()
+ if(!suit || !suit.damage || !suit.can_breach)
+ return
+
+ suit.breaches = list()
+ suit.calc_breach_damage()
+
+ return
+
+/obj/machinery/suit_cycler/verb/leave()
+ set name = "Eject Cycler"
+ set category = "Object"
+ set src in oview(1)
+
+ if (usr.stat != 0)
+ return
+
+ eject_occupant(usr)
+
+/obj/machinery/suit_cycler/proc/eject_occupant(mob/user as mob)
+
+ if(locked || active)
+ user << "\red The cycler is locked."
+ return
+
+ if (!occupant)
+ return
+
+ if (occupant.client)
+ occupant.client.eye = occupant.client.mob
+ occupant.client.perspective = MOB_PERSPECTIVE
+
+ occupant.loc = get_turf(occupant)
+ occupant = null
+
+ add_fingerprint(usr)
+ src.updateUsrDialog()
+ src.update_icon()
+
+ return
+
+//HACKING PROCS, MOSTLY COPIED FROM VENDING MACHINES
+/obj/machinery/suit_cycler/proc/isWireColorCut(var/wireColor)
+ var/wireFlag = APCWireColorToFlag[wireColor]
+ return ((src.wires & wireFlag) == 0)
+
+/obj/machinery/suit_cycler/proc/isWireCut(var/wireIndex)
+ var/wireFlag = APCIndexToFlag[wireIndex]
+ return ((src.wires & wireFlag) == 0)
+
+/obj/machinery/suit_cycler/proc/cut(var/wireColor)
+ var/wireFlag = APCWireColorToFlag[wireColor]
+ var/wireIndex = APCWireColorToIndex[wireColor]
+ src.wires &= ~wireFlag
+ switch(wireIndex)
+
+ if(WIRE_EXTEND)
+ safeties = 0
+ if(WIRE_SHOCK)
+ electrified = -1
+ if (WIRE_SCANID)
+ locked = 0
+
+/obj/machinery/suit_cycler/proc/mend(var/wireColor)
+ var/wireFlag = APCWireColorToFlag[wireColor]
+ var/wireIndex = APCWireColorToIndex[wireColor] //not used in this function
+ src.wires |= wireFlag
+ switch(wireIndex)
+ if(WIRE_SHOCK)
+ src.electrified = 0
+
+/obj/machinery/suit_cycler/proc/pulse(var/wireColor)
+ var/wireIndex = APCWireColorToIndex[wireColor]
+ switch(wireIndex)
+ if(WIRE_EXTEND)
+ safeties = !locked
+ if(WIRE_SHOCK)
+ electrified = 30
+ if (WIRE_SCANID)
+ locked = !locked
+
+/obj/machinery/suit_cycler/proc/shock(mob/user, prb)
+ if(stat & (BROKEN|NOPOWER)) // unpowered, no shock
+ return 0
+ if(!prob(prb))
+ return 0
+ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ if (electrocute_mob(user, get_area(src), src, 0.7))
+ return 1
+ else
+ return 0
+
+//There HAS to be a less bloated way to do this. TODO: some kind of table/icon name coding? ~Z
+/obj/machinery/suit_cycler/proc/apply_paintjob()
+
+ if(!target_species || !target_department)
+ return
+
+ switch(target_species)
+ if("Human" || "Skrell")
+ if(helmet) helmet.species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox")
+ if(suit) suit.species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox")
+ if("Unathi")
+ if(helmet) helmet.species_restricted = list("Unathi")
+ if(suit) suit.species_restricted = list("Unathi")
+ if("Tajaran")
+ if(helmet) helmet.species_restricted = list("Tajaran")
+ if(suit) suit.species_restricted = list("Tajaran")
+
+ if(target_department == "Engineering")
+
+ if(helmet) helmet.name = "engineering hardsuit helmet"
+ if(suit) suit.name = "engineering hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-engineering"
+ helmet.item_state = "eng_helm"
+ helmet.item_color = "engineering"
+ if(suit)
+ suit.icon_state = "rig-engineering"
+ suit.item_state = "eng_hardsuit"
+ if("Skrell")
+ return //TODO
+ if("Unathi")
+ return //TODO
+ if("Tajaran")
+ if(helmet)
+ helmet.icon_state = "rig0-taj-helmet"
+ helmet.item_state = "rig0-taj-helmet"
+ helmet.item_color = "taj-helmet"
+ if(suit)
+ suit.icon_state = "rig-taj"
+ suit.item_state = "rig-taj"
+
+ else if(target_department == "Mining")
+
+ if(helmet) helmet.name = "mining hardsuit helmet"
+ if(suit) suit.name = "mining hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-mining"
+ helmet.item_state = "mining_helm"
+ helmet.item_color = "mining"
+ if(suit)
+ suit.icon_state = "rig-mining"
+ suit.item_state = "mining_hardsuit"
+ if("Skrell")
+ return //TODO
+ if("Unathi")
+ return //TODO
+ if("Tajaran")
+ return //TODO
+
+ else if(target_department == "^%###^%$")
+
+ if(helmet) helmet.name = "blood-red hardsuit helmet"
+ if(suit) suit.name = "blood-red hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-syndie-human"
+ helmet.item_state = "syndie_helm"
+ helmet.item_color = "syndie-human"
+ if(suit)
+ suit.item_state = "syndie_hardsuit"
+ suit.icon_state = "rig-syndie-human"
+ if("Skrell")
+ if(helmet)
+ helmet.icon_state = "rig0-syndie-skrell"
+ helmet.item_state = "syndie_helm"
+ helmet.item_color = "syndie-skrell"
+ if(suit)
+ suit.item_state = "syndie_hardsuit"
+ suit.icon_state = "rig-syndie-skrell"
+ if("Unathi")
+ if(helmet)
+ helmet.icon_state = "rig0-syndie-unathi"
+ helmet.item_state = "syndie_helm"
+ helmet.item_color = "syndie-unathi"
+ if(suit)
+ suit.item_state = "syndie_hardsuit"
+ suit.icon_state = "rig-syndie-unathi"
+ if("Tajaran")
+ if(helmet)
+ helmet.icon_state = "rig0-syndie-taj"
+ helmet.item_state = "syndie_helm"
+ helmet.item_color = "syndie-taj"
+ if(suit)
+ suit.item_state = "syndie_hardsuit"
+ suit.icon_state = "rig-syndie-taj"
+
+ else if(target_department == "Medical")
+
+ if(helmet) helmet.name = "medical hardsuit helmet"
+ if(suit) suit.name = "medical hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-medical"
+ helmet.item_state = "medical_helm"
+ helmet.item_color = "medical"
+ if(suit)
+ suit.icon_state = "rig-medical"
+ suit.item_state = "medical_hardsuit"
+ if("Skrell")
+ return //TODO
+ if("Unathi")
+ return //TODO
+ if("Tajaran")
+ return //TODO
+
+ else if(target_department == "Security")
+
+ if(helmet) helmet.name = "security hardsuit helmet"
+ if(suit) suit.name = "security hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-sec"
+ helmet.item_state = "sec_helm"
+ helmet.item_color = "sec"
+ if(suit)
+ suit.icon_state = "rig-sec"
+ suit.item_state = "sec_hardsuit"
+ if("Skrell")
+ return //TODO
+ if("Unathi")
+ return //TODO
+ if("Tajaran")
+ return //TODO
+
+ else if(target_department == "Atmos")
+
+ if(helmet) helmet.name = "atmospherics hardsuit helmet"
+ if(suit) suit.name = "atmospherics hardsuit"
+
+ switch(target_species)
+ if("Human")
+ if(helmet)
+ helmet.icon_state = "rig0-atmos"
+ helmet.item_state = "atmos_helm"
+ helmet.item_color = "atmos"
+ if(suit)
+ suit.icon_state = "rig-atmos"
+ suit.item_state = "atmos_hardsuit"
+ if("Skrell")
+ return //TODO
+ if("Unathi")
+ return //TODO
+ if("Tajaran")
+ return //TODO
\ No newline at end of file
diff --git a/code/modules/clothing/spacesuits/breaches.dm b/code/modules/clothing/spacesuits/breaches.dm
new file mode 100644
index 00000000000..19f795561c0
--- /dev/null
+++ b/code/modules/clothing/spacesuits/breaches.dm
@@ -0,0 +1,221 @@
+//A 'wound' system for space suits.
+//Breaches greatly increase the amount of lost gas and decrease the armour rating of the suit.
+//They can be healed with plastic or metal sheeting.
+
+/datum/breach
+ var/class = 0 // Size. Lower is smaller.
+ var/descriptor // 'gaping hole' etc.
+ var/damtype = BURN // Punctured or melted
+ var/obj/item/clothing/suit/space/holder // Suit containing the list of breaches holding this instance.
+
+/obj/item/clothing/suit/space
+
+ var/can_breach = 1 // Set to 0 to disregard all breaching.
+ var/list/breaches = list() // Breach datum container.
+ var/resilience = 0.2 // Multiplier that turns damage into breach class. 1 is 100% of damage to breach, 0.1 is 10%.
+ var/breach_threshold = 3 // Min damage before a breach is possible.
+ var/damage = 0 // Current total damage
+ var/brute_damage = 0 // Specifically brute damage.
+ var/burn_damage = 0 // Specifically burn damage.
+ var/base_name // Used to keep the original name safe while we apply modifiers.
+
+/obj/item/clothing/suit/space/New()
+ ..()
+ base_name = "[name]"
+
+//Some simple descriptors for breaches. Global because lazy, TODO: work out a better way to do this.
+
+var/global/list/breach_brute_descriptors = list(
+ "tiny puncture",
+ "ragged tear",
+ "large split",
+ "huge tear",
+ "gaping wound"
+ )
+
+var/global/list/breach_burn_descriptors = list(
+ "small burn",
+ "melted patch",
+ "sizable burn",
+ "large scorched area",
+ "huge scorched area"
+ )
+
+/datum/breach/proc/update_descriptor()
+
+ //Sanity...
+ class = max(1,min(class,5))
+ //Apply the correct descriptor.
+ if(damtype == BURN)
+ descriptor = breach_burn_descriptors[class]
+ else if(damtype == BRUTE)
+ descriptor = breach_brute_descriptors[class]
+
+//Repair a certain amount of brute or burn damage to the suit.
+/obj/item/clothing/suit/space/proc/repair_breaches(var/damtype, var/amount, var/mob/user)
+
+ if(!can_breach || !breaches || !breaches.len || !damage)
+ user << "There are no breaches to repair on \the [src]."
+ return
+
+ var/list/valid_breaches = list()
+
+ for(var/datum/breach/B in breaches)
+ if(B.damtype == damtype)
+ valid_breaches += B
+
+ if(!valid_breaches.len)
+ user << "There are no breaches to repair on \the [src]."
+ return
+
+ var/amount_left = amount
+ for(var/datum/breach/B in valid_breaches)
+ if(!amount_left) break
+
+ if(B.class <= amount_left)
+ amount_left -= B.class
+ valid_breaches -= B
+ breaches -= B
+ else
+ B.class -= amount_left
+ amount_left = 0
+ B.update_descriptor()
+
+ user.visible_message("[user] patches some of the damage on \the [src].")
+ calc_breach_damage()
+
+/obj/item/clothing/suit/space/proc/create_breaches(var/damtype, var/amount)
+
+ if(!can_breach || !amount)
+ return
+
+ if(!breaches)
+ breaches = list()
+
+ if(damage > 25) return //We don't need to keep tracking it when it's at 250% pressure loss, really.
+
+ if(!loc) return
+ var/turf/T = get_turf(src)
+ if(!T) return
+
+ amount = amount * src.resilience
+
+ //Increase existing breaches.
+ for(var/datum/breach/existing in breaches)
+
+ if(existing.damtype != damtype)
+ continue
+
+ if (existing.class < 5)
+ var/needs = 5 - existing.class
+ if(amount < needs)
+ existing.class += amount
+ amount = 0
+ else
+ existing.class = 5
+ amount -= needs
+
+ if(existing.damtype == BRUTE)
+ T.visible_message("\The [existing.descriptor] on [src] gapes wider!")
+ else if(existing.damtype == BURN)
+ T.visible_message("\The [existing.descriptor] on [src] widens!")
+
+ if (amount)
+ //Spawn a new breach.
+ var/datum/breach/B = new()
+ breaches += B
+
+ B.class = min(amount,5)
+
+ B.damtype = damtype
+ B.update_descriptor()
+ B.holder = src
+
+ if(B.damtype == BRUTE)
+ T.visible_message("\A [B.descriptor] opens up on [src]!")
+ else if(B.damtype == BURN)
+ T.visible_message("\A [B.descriptor] marks the surface of [src]!")
+
+ calc_breach_damage()
+
+//Calculates the current extent of the damage to the suit.
+/obj/item/clothing/suit/space/proc/calc_breach_damage()
+
+ damage = 0
+ brute_damage = 0
+ burn_damage = 0
+
+ if(!can_breach || !breaches || !breaches.len)
+ name = base_name
+ return 0
+
+ for(var/datum/breach/B in breaches)
+ if(!B.class)
+ src.breaches -= B
+ del(B)
+ else
+ damage += B.class
+ if(B.damtype == BRUTE)
+ brute_damage += B.class
+ else if(B.damtype == BURN)
+ burn_damage += B.class
+
+ if(damage >= 3)
+ if(brute_damage >= 3 && brute_damage > burn_damage)
+ name = "punctured [base_name]"
+ else if(burn_damage >= 3 && burn_damage > brute_damage)
+ name = "scorched [base_name]"
+ else
+ name = "damaged [base_name]"
+ else
+ name = "[base_name]"
+
+ return damage
+
+//Handles repairs (and also upgrades).
+
+/obj/item/clothing/suit/space/attackby(obj/item/W as obj, mob/user as mob)
+ if(istype(W,/obj/item/stack/sheet/mineral/plastic) || istype(W,/obj/item/stack/sheet/metal))
+
+ if(istype(src.loc,/mob/living))
+ user << "\red How do you intend to patch a hardsuit while someone is wearing it?"
+ return
+
+ if(!damage || !burn_damage)
+ user << "There is no surface damage on \the [src] to repair."
+ return
+
+ var/obj/item/stack/sheet/P = W
+ if(P.amount < 3)
+ P.use(P.amount)
+ repair_breaches(BURN, ( istype(P,/obj/item/stack/sheet/mineral/plastic) ? P.amount : (P.amount*2) ), user)
+ else
+ P.use(3)
+ repair_breaches(BURN, ( istype(P,/obj/item/stack/sheet/mineral/plastic) ? 3 : 5), user)
+ return
+
+ else if(istype(W, /obj/item/weapon/weldingtool))
+
+ if(istype(src.loc,/mob/living))
+ user << "\red How do you intend to patch a hardsuit while someone is wearing it?"
+ return
+
+ if (!damage || ! brute_damage)
+ user << "There is no structural damage on \the [src] to repair."
+ return
+
+ var/obj/item/weapon/weldingtool/WT = W
+ if(!WT.remove_fuel(5))
+ user << "\red You need more welding fuel to repair this suit."
+ return
+
+ repair_breaches(BRUTE, 3, user)
+ return
+
+ ..()
+
+/obj/item/clothing/suit/space/examine()
+ ..()
+ if(can_breach && breaches && breaches.len)
+ for(var/datum/breach/B in breaches)
+ usr << "\red It has \a [B.descriptor]."
\ No newline at end of file
diff --git a/code/modules/clothing/spacesuits/rig.dm b/code/modules/clothing/spacesuits/rig.dm
index 9a527b32135..8a17cee5fb7 100644
--- a/code/modules/clothing/spacesuits/rig.dm
+++ b/code/modules/clothing/spacesuits/rig.dm
@@ -53,6 +53,226 @@
max_heat_protection_temperature = SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE
species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox")
+ //Breach thresholds, should ideally be inherited by most (if not all) hardsuits.
+ breach_threshold = 18
+ can_breach = 1
+
+ //Component/device holders.
+ var/obj/item/weapon/stock_parts/gloves = null // Basic capacitor allows insulation, upgrades allow shock gloves etc.
+
+ var/attached_boots = 1 // Can't wear boots if some are attached
+ var/obj/item/clothing/shoes/magboots/boots = null // Deployable boots, if any.
+ var/attached_helmet = 1 // Can't wear a helmet if one is deployable.
+ var/obj/item/clothing/head/helmet/helmet = null // Deployable helmet, if any.
+
+ var/list/max_mounted_devices = 0 // Maximum devices. Easy.
+ var/list/can_mount = null // Types of device that can be hardpoint mounted.
+ var/list/mounted_devices = null // Holder for the above device.
+ var/obj/item/active_device = null // Currently deployed device, if any.
+
+/obj/item/clothing/suit/space/rig/equipped(mob/M)
+ ..()
+
+ var/mob/living/carbon/human/H = M
+
+ if(!istype(H)) return
+
+ if(H.wear_suit != src)
+ return
+
+ if(attached_helmet && helmet)
+ if(H.head)
+ M << "You are unable to deploy your suit's helmet as \the [H.head] is in the way."
+ else
+ M << "Your suit's helmet deploys with a hiss."
+ //TODO: Species check, skull damage for forcing an unfitting helmet on?
+ helmet.loc = H
+ H.equip_to_slot(helmet, slot_head)
+ helmet.canremove = 0
+
+ if(attached_boots && boots)
+ if(H.shoes)
+ M << "You are unable to deploy your suit's magboots as \the [H.shoes] are in the way."
+ else
+ M << "Your suit's boots deploy with a hiss."
+ boots.loc = H
+ H.equip_to_slot(boots, slot_shoes)
+ boots.canremove = 0
+
+/obj/item/clothing/suit/space/rig/dropped()
+ ..()
+
+ var/mob/living/carbon/human/H
+
+ H = helmet.loc
+ if(istype(H))
+ if(helmet && H.head == helmet)
+ helmet.canremove = 1
+ H.drop_from_inventory(helmet)
+ helmet.loc = src
+
+ H = boots.loc
+ if(istype(H))
+ if(boots && H.shoes == boots)
+ boots.canremove = 1
+ H.drop_from_inventory(boots)
+ boots.loc = src
+
+/obj/item/clothing/suit/space/rig/verb/get_mounted_device()
+
+ set name = "Deploy Mounted Device"
+ set category = "Object"
+ set src in usr
+
+ if(!can_mount)
+ verbs -= /obj/item/clothing/suit/space/rig/verb/get_mounted_device
+ verbs -= /obj/item/clothing/suit/space/rig/verb/stow_mounted_device
+ return
+
+ if(!istype(usr, /mob/living)) return
+ if(usr.stat) return
+
+ if(active_device)
+ usr << "You already have \the [active_device] deployed."
+ return
+
+ if(!mounted_devices.len)
+ usr << "You do not have any devices mounted on \the [src]."
+ return
+
+/obj/item/clothing/suit/space/rig/verb/stow_mounted_device()
+
+ set name = "Stow Mounted Device"
+ set category = "Object"
+ set src in usr
+
+ if(!can_mount)
+ verbs -= /obj/item/clothing/suit/space/rig/verb/get_mounted_device
+ verbs -= /obj/item/clothing/suit/space/rig/verb/stow_mounted_device
+ return
+
+ if(!istype(usr, /mob/living)) return
+
+ if(usr.stat) return
+
+ if(!active_device)
+ usr << "You have no device currently deployed."
+ return
+
+/obj/item/clothing/suit/space/rig/verb/toggle_helmet()
+
+ set name = "Toggle Helmet"
+ set category = "Object"
+ set src in usr
+
+ if(!istype(src.loc,/mob/living)) return
+
+ if(!helmet)
+ usr << "There is no helmet installed."
+ return
+
+ var/mob/living/carbon/human/H = usr
+
+ if(!istype(H)) return
+ if(H.stat) return
+ if(H.wear_suit != src) return
+
+ if(H.head == helmet)
+ helmet.canremove = 1
+ H.drop_from_inventory(helmet)
+ helmet.loc = src
+ H << "\blue You retract your hardsuit helmet."
+ else
+ if(H.head)
+ H << "\red You cannot deploy your helmet while wearing another helmet."
+ return
+ //TODO: Species check, skull damage for forcing an unfitting helmet on?
+ helmet.loc = H
+ H.equip_to_slot(helmet, slot_head)
+ helmet.canremove = 0
+ H << "\blue You deploy your hardsuit helmet, sealing you off from the world."
+
+/obj/item/clothing/suit/space/rig/attackby(obj/item/W as obj, mob/user as mob)
+
+ if(!istype(user,/mob/living)) return
+
+ if(user.a_intent == "help")
+
+ if(istype(src.loc,/mob/living))
+ user << "How do you propose to modify a hardsuit while it is being worn?"
+ return
+
+ var/target_zone = user.zone_sel.selecting
+
+ if(target_zone == "head")
+
+ //Installing a component into or modifying the contents of the helmet.
+ if(!attached_helmet)
+ user << "\The [src] does not have a helmet mount."
+ return
+
+ if(istype(W,/obj/item/weapon/screwdriver))
+ if(!helmet)
+ user << "\The [src] does not have a helmet installed."
+ else
+ user << "You detatch \the [helmet] from \the [src]'s helmet mount."
+ helmet.loc = get_turf(src)
+ src.helmet = null
+ return
+ else if(istype(W,/obj/item/clothing/head/helmet/space))
+ if(helmet)
+ user << "\The [src] already has a helmet installed."
+ else
+ user << "You attach \the [W] to \the [src]'s helmet mount."
+ user.drop_item()
+ W.loc = src
+ src.helmet = W
+ return
+ else
+ return ..()
+
+ else if(target_zone == "l_leg" || target_zone == "r_leg" || target_zone == "l_foot" || target_zone == "r_foot")
+
+ //Installing a component into or modifying the contents of the feet.
+ if(!attached_boots)
+ user << "\The [src] does not have boot mounts."
+ return
+
+ if(istype(W,/obj/item/weapon/screwdriver))
+ if(!boots)
+ user << "\The [src] does not have any boots installed."
+ else
+ user << "You detatch \the [boots] from \the [src]'s boot mounts."
+ boots.loc = get_turf(src)
+ boots = null
+ return
+ else if(istype(W,/obj/item/clothing/shoes/magboots))
+ if(boots)
+ user << "\The [src] already has magboots installed."
+ else
+ user << "You attach \the [W] to \the [src]'s boot mounts."
+ user.drop_item()
+ W.loc = src
+ boots = W
+ else
+ return ..()
+
+ else if(target_zone == "l_arm" || target_zone == "r_arm" || target_zone == "l_hand" || target_zone == "r_hand")
+
+ //Installing a component into or modifying the contents of the hands.
+
+ else if(target_zone == "torso" || target_zone == "groin")
+
+ //Modifying the cell or mounted devices
+
+ if(!mounted_devices)
+ return
+
+ else //wat
+ return ..()
+
+ ..()
+
//Chief Engineer's rig
/obj/item/clothing/head/helmet/space/rig/elite
name = "advanced hardsuit helmet"
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 1b12f625620..3583b6822e4 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -229,6 +229,8 @@ This function restores all organs.
/mob/living/carbon/human/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/sharp = 0, var/obj/used_weapon = null)
+ handle_suit_punctures(damagetype, damage)
+
//visible_message("Hit debug. [damage] | [damagetype] | [def_zone] | [blocked] | [sharp] | [used_weapon]")
if((damagetype != BRUTE) && (damagetype != BURN))
..(damage, damagetype, def_zone, blocked)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 44deb248ca8..12a16567c56 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -53,6 +53,7 @@ emp_act
if(check_shields(P.damage, "the [P.name]"))
P.on_hit(src, 2, def_zone)
+ handle_suit_punctures(P.damage_type, P.damage)
return 2
//BEGIN BOOK'S TASER NERF.
@@ -296,3 +297,14 @@ emp_act
if(w_uniform)
w_uniform.add_blood(source)
update_inv_w_uniform(0)
+
+/mob/living/carbon/human/proc/handle_suit_punctures(var/damtype, var/damage)
+
+ if(!wear_suit) return
+ if(!istype(wear_suit,/obj/item/clothing/suit/space)) return
+ if(damtype != BURN && damtype != BRUTE) return
+
+ var/obj/item/clothing/suit/space/SS = wear_suit
+ var/penetrated_dam = max(0,(damage - max(0,(SS.breach_threshold - SS.damage))))
+
+ if(penetrated_dam) SS.create_breaches(damtype, penetrated_dam)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 036a84b8b1a..a687940ea2b 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -133,12 +133,24 @@
var/pressure_difference = abs( pressure - ONE_ATMOSPHERE )
var/pressure_adjustment_coefficient = 1 //Determins how much the clothing you are wearing protects you in percent.
- if(wear_suit && (wear_suit.flags & STOPSPRESSUREDMAGE))
- pressure_adjustment_coefficient -= PRESSURE_SUIT_REDUCTION_COEFFICIENT
+
if(head && (head.flags & STOPSPRESSUREDMAGE))
pressure_adjustment_coefficient -= PRESSURE_HEAD_REDUCTION_COEFFICIENT
- pressure_adjustment_coefficient = max(pressure_adjustment_coefficient,0) //So it isn't less than 0
+
+ if(wear_suit && (wear_suit.flags & STOPSPRESSUREDMAGE))
+ pressure_adjustment_coefficient -= PRESSURE_SUIT_REDUCTION_COEFFICIENT
+
+ //Handles breaches in your space suit. 10 suit damage equals a 100% loss of pressure reduction.
+ if(istype(wear_suit,/obj/item/clothing/suit/space))
+ var/obj/item/clothing/suit/space/S = wear_suit
+ if(S.can_breach && S.damage)
+ var/pressure_loss = S.damage * 0.1
+ pressure_adjustment_coefficient += pressure_loss
+
+ pressure_adjustment_coefficient = min(1,max(pressure_adjustment_coefficient,0)) //So it isn't less than 0 or larger than 1.
+
pressure_difference = pressure_difference * pressure_adjustment_coefficient
+
if(pressure > ONE_ATMOSPHERE)
return ONE_ATMOSPHERE + pressure_difference
else