Files
Bubberstation/code/modules/surgery/organs/internal/lungs/_lungs.dm
Unit2E 8b3f03809d Bumps up Nitrium reagent addition on breath so people don't lose sleep immunity (#90345)
## About The Pull Request
webedit hihi

Nitrium is the anti-sleepy gas (at high pp), however, some effects have
negative consequences for how good it is at its job. Lag (occasionally),
and crucially, eating will make it so the reagent is metab'd out of the
mob for just long enough that they lose the sleep immunity. This creates
a frustrating loop where you're falling asleep for just a moment every
few seconds when breathing in a mix of nitrium and healium, which are
meant to synergize together. To fix this, this bumps up the amount of
reagent added to a max of 2, up from 1, bringing it in line with the
lower pp version (which already adds 2 of itself, and doesn't have a
similar issue).

## Why It's Good For The Game
It's frustrating for players to create a gas mix that arbitrarily
doesn't work, especially when caused by eating, often disabling the
proper functioning of Nitrium for the full duration of whatever kind of
"good food" status that is granted. This fixes that minor frustration.


## Changelog

🆑

fix: Mobs should no longer fall asleep when breathing in enough Nitrium
to provide sleep immunity.

/🆑
2025-04-29 17:15:52 -06:00

1071 lines
48 KiB
Plaintext

/obj/item/organ/lungs
name = "lungs"
icon_state = "lungs"
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_LUNGS
gender = PLURAL
w_class = WEIGHT_CLASS_SMALL
var/respiration_type = NONE // The type(s) of gas this lung needs for respiration
healing_factor = STANDARD_ORGAN_HEALING
decay_factor = STANDARD_ORGAN_DECAY * 0.9 // fails around 16.5 minutes, lungs are one of the last organs to die (of the ones we have)
low_threshold_passed = span_warning("You feel short of breath.")
high_threshold_passed = span_warning("You feel some sort of constriction around your chest as your breathing becomes shallow and rapid.")
now_fixed = span_warning("Your lungs seem to once again be able to hold air.")
low_threshold_cleared = span_info("You can breathe normally again.")
high_threshold_cleared = span_info("The constriction around your chest loosens as your breathing calms down.")
var/failed = FALSE
var/operated = FALSE //whether we can still have our damages fixed through surgery
food_reagents = list(/datum/reagent/consumable/nutriment/organ_tissue = 5, /datum/reagent/medicine/salbutamol = 5)
/// Our previous breath's partial pressures, in the form gas id -> partial pressure
var/list/last_partial_pressures = list()
/// List of gas to treat as other gas, in the form list(inital_gas, treat_as, multiplier)
var/list/treat_as = list()
/// Assoc list of procs to run while a gas is present, in the form gas id -> proc_path
var/list/breath_present = list()
/// Assoc list of procs to run when a gas is immediately removed from the breath, in the form gas id -> proc_path
var/list/breath_lost = list()
/// Assoc list of procs to always run, in the form gas_id -> proc_path
var/list/breathe_always = list()
/// Gas mixture to breath out when we're done processing a breath
/// Will get emptied out when it's all done
var/datum/gas_mixture/immutable/breath_out
//Breath damage
//These thresholds are checked against what amounts to total_mix_pressure * (gas_type_mols/total_mols)
var/safe_oxygen_min = 16 // Minimum safe partial pressure of O2, in kPa
var/safe_oxygen_max = 0
var/safe_nitro_min = 0
var/safe_co2_max = 10 // Yes it's an arbitrary value who cares?
var/safe_plasma_min = 0
///How much breath partial pressure is a safe amount of plasma. 0 means that we are immune to plasma.
var/safe_plasma_max = 0.05
var/n2o_detect_min = 0.08 //Minimum n2o for effects
var/n2o_para_min = 1 //Sleeping agent
var/n2o_sleep_min = 5 //Sleeping agent
var/BZ_trip_balls_min = 1 //BZ gas
var/BZ_brain_damage_min = 10 //Give people some room to play around without killing the station
var/gas_stimulation_min = 0.002 // For, Pluoxium, Nitrium and Freon
///Minimum amount of healium to make you unconscious for 4 seconds
var/healium_para_min = 3
///Minimum amount of healium to knock you down for good
var/healium_sleep_min = 6
///Minimum amount of helium to affect speech
var/helium_speech_min = 5
///Whether these lungs react negatively to miasma
var/suffers_miasma = TRUE
// Vars for N2O/healium induced euphoria, stun, and sleep.
var/n2o_euphoria = EUPHORIA_LAST_FLAG
var/healium_euphoria = EUPHORIA_LAST_FLAG
var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/oxy_damage_type = OXY
var/nitro_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/nitro_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/nitro_damage_type = OXY
var/co2_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/co2_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/co2_damage_type = OXY
var/plas_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/plas_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
var/plas_damage_type = TOX
var/tritium_irradiation_moles_min = 1
var/tritium_irradiation_moles_max = 15
var/tritium_irradiation_probability_min = 10
var/tritium_irradiation_probability_max = 60
var/cold_message = "your face freezing and an icicle forming"
var/cold_level_1_threshold = 260
var/cold_level_2_threshold = 200
var/cold_level_3_threshold = 120
var/cold_level_1_damage = COLD_GAS_DAMAGE_LEVEL_1 //Keep in mind with gas damage levels, you can set these to be negative, if you want someone to heal, instead.
var/cold_level_2_damage = COLD_GAS_DAMAGE_LEVEL_2
var/cold_level_3_damage = COLD_GAS_DAMAGE_LEVEL_3
var/cold_damage_type = BURN
var/hot_message = "your face burning and a searing heat"
var/heat_level_1_threshold = 360
var/heat_level_2_threshold = 400
var/heat_level_3_threshold = 1000
var/heat_level_1_damage = HEAT_GAS_DAMAGE_LEVEL_1
var/heat_level_2_damage = HEAT_GAS_DAMAGE_LEVEL_2
var/heat_level_3_damage = HEAT_GAS_DAMAGE_LEVEL_3
var/heat_damage_type = BURN
var/crit_stabilizing_reagent = /datum/reagent/medicine/epinephrine
// assign the respiration_type
/obj/item/organ/lungs/Initialize(mapload)
. = ..()
breath_out = new(BREATH_VOLUME)
if(safe_nitro_min)
respiration_type |= RESPIRATION_N2
if(safe_oxygen_min)
respiration_type |= RESPIRATION_OXYGEN
if(safe_plasma_min)
respiration_type |= RESPIRATION_PLASMA
// Sets up what gases we want to react to, and in what way
// always is always processed, while_present is called when the gas is in the breath, and on_loss is called right after a gas is lost
// The naming convention goes like this
// always : breath_{gas_type}
// safe/neutral while_present : consume_{gas_type}
// safe/neutral on_loss : lose_{gas_type}
// dangerous while_present : too_much_{gas_type}
// dangerous on_loss : safe_{gas_type}
// These are reccomendations, if something seems better feel free to ignore them. S a bit vibes based
if(safe_oxygen_min)
add_gas_reaction(/datum/gas/oxygen, always = PROC_REF(breathe_oxygen))
if(safe_oxygen_max)
add_gas_reaction(/datum/gas/oxygen, while_present = PROC_REF(too_much_oxygen), on_loss = PROC_REF(safe_oxygen))
add_gas_reaction(/datum/gas/pluoxium, while_present = PROC_REF(consume_pluoxium))
// We treat a mole of ploux as 8 moles of oxygen
add_gas_relationship(/datum/gas/pluoxium, /datum/gas/oxygen, 8)
if(safe_nitro_min)
add_gas_reaction(/datum/gas/nitrogen, always = PROC_REF(breathe_nitro))
if(safe_co2_max)
add_gas_reaction(/datum/gas/carbon_dioxide, while_present = PROC_REF(too_much_co2), on_loss = PROC_REF(safe_co2))
if(safe_plasma_min)
add_gas_reaction(/datum/gas/plasma, always = PROC_REF(breathe_plasma))
if(safe_plasma_max)
add_gas_reaction(/datum/gas/plasma, while_present = PROC_REF(too_much_plasma), on_loss = PROC_REF(safe_plasma))
add_gas_reaction(/datum/gas/bz, while_present = PROC_REF(too_much_bz))
add_gas_reaction(/datum/gas/freon, while_present = PROC_REF(too_much_freon))
add_gas_reaction(/datum/gas/halon, while_present = PROC_REF(too_much_halon))
add_gas_reaction(/datum/gas/healium, while_present = PROC_REF(consume_healium), on_loss = PROC_REF(lose_healium))
add_gas_reaction(/datum/gas/helium, while_present = PROC_REF(consume_helium), on_loss = PROC_REF(lose_helium))
add_gas_reaction(/datum/gas/hypernoblium, while_present = PROC_REF(consume_hypernoblium))
if(suffers_miasma)
add_gas_reaction(/datum/gas/miasma, while_present = PROC_REF(too_much_miasma), on_loss = PROC_REF(safe_miasma))
add_gas_reaction(/datum/gas/nitrous_oxide, while_present = PROC_REF(too_much_n2o), on_loss = PROC_REF(safe_n2o))
add_gas_reaction(/datum/gas/nitrium, while_present = PROC_REF(too_much_nitrium))
add_gas_reaction(/datum/gas/tritium, while_present = PROC_REF(too_much_tritium))
add_gas_reaction(/datum/gas/zauker, while_present = PROC_REF(too_much_zauker))
///Simply exists so that you don't keep any alerts from your previous lack of lungs.
/obj/item/organ/lungs/on_mob_insert(mob/living/carbon/receiver, special = FALSE, movement_flags)
. = ..()
receiver.clear_alert(ALERT_NOT_ENOUGH_OXYGEN)
receiver.clear_alert(ALERT_NOT_ENOUGH_CO2)
receiver.clear_alert(ALERT_NOT_ENOUGH_NITRO)
receiver.clear_alert(ALERT_NOT_ENOUGH_PLASMA)
receiver.clear_alert(ALERT_NOT_ENOUGH_N2O)
/obj/item/organ/lungs/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
. = ..()
// This is very "manual" I realize, but it's useful to ensure cleanup for gases we're removing happens
// Avoids stuck alerts and such
var/static/datum/gas_mixture/immutable/dummy = new(BREATH_VOLUME)
for(var/gas_id in last_partial_pressures)
var/on_loss = breath_lost[gas_id]
if(!on_loss)
continue
call(src, on_loss)(organ_owner, dummy, last_partial_pressures[gas_id])
dummy.garbage_collect()
/**
* Tells the lungs to pay attention to the passed in gas type
* We'll check for it when breathing, in a few possible ways
* Accepts 3 optional arguments:
*
* proc/while_present * Called while the gas is present in our breath. Return BREATH_LOST to call on_loss afterwards
* proc/on_loss * Called after we have lost a gas from our breath.
* proc/always * Always called. Best suited for breathing procs, like oxygen
*
* while_present and always get the same arguments (mob/living/carbon/breather, datum/gas_mixture/breath, pp, old_pp)
* on_loss is almost exactly the same, except it doesn't pass in a current partial pressure, since one isn't avalible
*/
/obj/item/organ/lungs/proc/add_gas_reaction(gas_type, while_present = null, on_loss = null, always = null)
if(always)
breathe_always[gas_type] = always
if(isnull(while_present) && isnull(on_loss))
return
if(while_present)
breath_present[gas_type] = while_present
if(on_loss)
breath_lost[gas_type] = on_loss
#define BREATH_RELATIONSHIP_INITIAL_GAS 1
#define BREATH_RELATIONSHIP_CONVERT 2
#define BREATH_RELATIONSHIP_MULTIPLIER 3
/**
* Tells the lungs to treat the passed in gas type as another passed in gas type
* Takes the gas to check for as an argument, alongside the gas to convert and the multiplier to use
* These act in the order of insertion, use that how you will
*/
/obj/item/organ/lungs/proc/add_gas_relationship(gas_type, convert_to, multiplier)
if(isnull(gas_type) || isnull(convert_to) || multiplier == 0)
return
var/list/add = new /list(BREATH_RELATIONSHIP_MULTIPLIER)
add[BREATH_RELATIONSHIP_INITIAL_GAS] = gas_type
add[BREATH_RELATIONSHIP_CONVERT] = convert_to
add[BREATH_RELATIONSHIP_MULTIPLIER] = multiplier
treat_as += list(add)
/// Clears away a gas relationship. Takes the same args as the initial addition
/obj/item/organ/lungs/proc/remove_gas_relationship(gas_type, convert_to, multiplier)
if(isnull(gas_type) || isnull(convert_to) || multiplier == 0)
return
for(var/packet in treat_as)
if(packet[BREATH_RELATIONSHIP_INITIAL_GAS] != gas_type)
continue
if(packet[BREATH_RELATIONSHIP_CONVERT] != convert_to)
continue
if(packet[BREATH_RELATIONSHIP_MULTIPLIER] != multiplier)
continue
treat_as -= packet
return
/// Handles oxygen breathing. Always called by things that need o2, no matter what
/obj/item/organ/lungs/proc/breathe_oxygen(mob/living/carbon/breather, datum/gas_mixture/breath, o2_pp, old_o2_pp)
if(o2_pp < safe_oxygen_min && !HAS_TRAIT(src, TRAIT_SPACEBREATHING))
// Not safe to check the old pp because of can_breath_vacuum
breather.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy)
var/gas_breathed = handle_suffocation(breather, o2_pp, safe_oxygen_min, breath.gases[/datum/gas/oxygen][MOLES])
if(o2_pp)
breathe_gas_volume(breath, /datum/gas/oxygen, /datum/gas/carbon_dioxide, volume = gas_breathed)
return
// If we used to not have enough, clear the alert
// Note this can be redundant, because of the vacuum check. It is fail safe tho, so it's ok
if(old_o2_pp < safe_oxygen_min)
breather.failed_last_breath = FALSE
breather.clear_alert(ALERT_NOT_ENOUGH_OXYGEN)
breathe_gas_volume(breath, /datum/gas/oxygen, /datum/gas/carbon_dioxide)
// Heal mob if not in crit.
if(breather.health >= breather.crit_threshold && breather.oxyloss)
breather.adjustOxyLoss(-5)
/// Maximum Oxygen effects. "Too much O2!"
/obj/item/organ/lungs/proc/too_much_oxygen(mob/living/carbon/breather, datum/gas_mixture/breath, o2_pp, old_o2_pp)
// If too much Oxygen is poisonous.
if(o2_pp <= safe_oxygen_max)
if(old_o2_pp > safe_oxygen_max)
return BREATH_LOST
return
var/ratio = (breath.gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10
breather.apply_damage(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type, spread_damage = TRUE)
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy)
/// Handles NOT having too much o2. only relevant if safe_oxygen_max has a value
/obj/item/organ/lungs/proc/safe_oxygen(mob/living/carbon/breather, datum/gas_mixture/breath, old_o2_pp)
breather.clear_alert(ALERT_TOO_MUCH_OXYGEN)
/// Behaves like Oxygen with 8X efficacy, but metabolizes into a reagent.
/obj/item/organ/lungs/proc/consume_pluoxium(mob/living/carbon/breather, datum/gas_mixture/breath, pluoxium_pp, old_pluoxium_pp)
breathe_gas_volume(breath, /datum/gas/pluoxium)
// Metabolize to reagent.
if(pluoxium_pp > gas_stimulation_min)
var/existing = breather.reagents.get_reagent_amount(/datum/reagent/pluoxium)
breather.reagents.add_reagent(/datum/reagent/pluoxium, max(0, 1 - existing))
/// If the lungs need Nitrogen to breathe properly, N2 is exchanged with CO2.
/obj/item/organ/lungs/proc/breathe_nitro(mob/living/carbon/breather, datum/gas_mixture/breath, nitro_pp, old_nitro_pp)
if(nitro_pp < safe_nitro_min && !HAS_TRAIT(src, TRAIT_SPACEBREATHING))
// Suffocation side-effects.
// Not safe to check the old pp because of can_breath_vacuum
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro)
var/gas_breathed = handle_suffocation(breather, nitro_pp, safe_nitro_min, breath.gases[/datum/gas/nitrogen][MOLES])
if(nitro_pp)
breathe_gas_volume(breath, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, volume = gas_breathed)
return
if(old_nitro_pp < safe_nitro_min)
breather.failed_last_breath = FALSE
breather.clear_alert(ALERT_NOT_ENOUGH_NITRO)
// Inhale N2, exhale equivalent amount of CO2. Look ma, sideways breathing!
breathe_gas_volume(breath, /datum/gas/nitrogen, /datum/gas/carbon_dioxide)
// Heal mob if not in crit.
if(breather.health >= breather.crit_threshold && breather.oxyloss)
breather.adjustOxyLoss(-5)
/// Maximum CO2 effects. "Too much CO2!"
/obj/item/organ/lungs/proc/too_much_co2(mob/living/carbon/breather, datum/gas_mixture/breath, co2_pp, old_co2_pp)
if(co2_pp <= safe_co2_max)
if(old_co2_pp > safe_co2_max)
return BREATH_LOST
return
// If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so.
if(old_co2_pp < safe_co2_max)
breather.co2overloadtime = world.time
// CO2 side-effects.
// Give the mob a chance to notice.
if(prob(20))
breather.emote("cough")
if((world.time - breather.co2overloadtime) > 12 SECONDS)
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2)
breather.Unconscious(6 SECONDS)
// Lets hurt em a little, let them know we mean business.
breather.apply_damage(3, co2_damage_type, spread_damage = TRUE)
// They've been in here 30s now, start to kill them for their own good!
if((world.time - breather.co2overloadtime) > 30 SECONDS)
breather.apply_damage(8, co2_damage_type, spread_damage = TRUE)
/// Handles NOT having too much co2. only relevant if safe_co2_max has a value
/obj/item/organ/lungs/proc/safe_co2(mob/living/carbon/breather, datum/gas_mixture/breath, old_co2_pp)
// Reset side-effects.
breather.co2overloadtime = 0
breather.clear_alert(ALERT_TOO_MUCH_CO2)
/// If the lungs need Plasma to breathe properly, Plasma is exchanged with CO2.
/obj/item/organ/lungs/proc/breathe_plasma(mob/living/carbon/breather, datum/gas_mixture/breath, plasma_pp, old_plasma_pp)
// Suffocation side-effects.
if(plasma_pp < safe_plasma_min && !HAS_TRAIT(src, TRAIT_SPACEBREATHING))
// Could check old_plasma_pp but vacuum breathing hates me
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas)
// Breathe insufficient amount of Plasma, exhale CO2.
var/gas_breathed = handle_suffocation(breather, plasma_pp, safe_plasma_min, breath.gases[/datum/gas/plasma][MOLES])
if(plasma_pp)
breathe_gas_volume(breath, /datum/gas/plasma, /datum/gas/carbon_dioxide, volume = gas_breathed)
return
if(old_plasma_pp < safe_plasma_min)
breather.failed_last_breath = FALSE
breather.clear_alert(ALERT_NOT_ENOUGH_PLASMA)
// Inhale Plasma, exhale equivalent amount of CO2.
breathe_gas_volume(breath, /datum/gas/plasma, /datum/gas/carbon_dioxide)
// Heal mob if not in crit.
if(breather.health >= breather.crit_threshold && breather.oxyloss)
breather.adjustOxyLoss(-5)
/// Maximum Plasma effects. "Too much Plasma!"
/obj/item/organ/lungs/proc/too_much_plasma(mob/living/carbon/breather, datum/gas_mixture/breath, plasma_pp, old_plasma_pp)
if(plasma_pp <= safe_plasma_max)
if(old_plasma_pp > safe_plasma_max)
return BREATH_LOST
return
// If it's the first breath with too much CO2 in it, lets start a counter, then have them pass out after 12s or so.
if(old_plasma_pp < safe_plasma_max)
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas)
var/ratio = (breath.gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10
breather.apply_damage(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type, spread_damage = TRUE)
/// Resets plasma side effects
/obj/item/organ/lungs/proc/safe_plasma(mob/living/carbon/breather, datum/gas_mixture/breath, old_plasma_pp)
breather.clear_alert(ALERT_TOO_MUCH_PLASMA)
/// Too much funny gas, time to get brain damage
/obj/item/organ/lungs/proc/too_much_bz(mob/living/carbon/breather, datum/gas_mixture/breath, bz_pp, old_bz_pp)
if(bz_pp > BZ_trip_balls_min)
breather.reagents.add_reagent(/datum/reagent/bz_metabolites, clamp(bz_pp, 1, 5))
if(bz_pp > BZ_brain_damage_min && prob(33))
breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150, ORGAN_ORGANIC)
/// Breathing in refridgerator coolent, shit's caustic
/obj/item/organ/lungs/proc/too_much_freon(mob/living/carbon/breather, datum/gas_mixture/breath, freon_pp, old_freon_pp)
// Inhale Freon. Exhale nothing.
breathe_gas_volume(breath, /datum/gas/freon)
if (freon_pp > gas_stimulation_min)
breather.reagents.add_reagent(/datum/reagent/freon, 1)
if (prob(freon_pp))
to_chat(breather, span_alert("Your mouth feels like it's burning!"))
if (freon_pp > 40)
breather.emote("gasp")
breather.adjustFireLoss(15)
if (prob(freon_pp / 2))
to_chat(breather, span_alert("Your throat closes up!"))
breather.set_silence_if_lower(6 SECONDS)
else
breather.adjustFireLoss(freon_pp / 4)
/// Breathing in halon, convert it to a reagent
/obj/item/organ/lungs/proc/too_much_halon(mob/living/carbon/breather, datum/gas_mixture/breath, halon_pp, old_halon_pp)
// Inhale Halon. Exhale nothing.
breathe_gas_volume(breath, /datum/gas/halon)
// Metabolize to reagent.
if(halon_pp > gas_stimulation_min)
breather.adjustOxyLoss(5)
breather.reagents.add_reagent(/datum/reagent/halon, max(0, 1 - breather.reagents.get_reagent_amount(/datum/reagent/halon)))
/// Sleeping gas with healing properties.
/obj/item/organ/lungs/proc/consume_healium(mob/living/carbon/breather, datum/gas_mixture/breath, healium_pp, old_healium_pp)
breathe_gas_volume(breath, /datum/gas/healium)
// Euphoria side-effect.
if(healium_pp > gas_stimulation_min)
if(prob(15))
to_chat(breather, span_alert("Your head starts spinning and your lungs burn!"))
healium_euphoria = EUPHORIA_ACTIVE
breather.emote("gasp")
else
healium_euphoria = EUPHORIA_INACTIVE
// Stun/Sleep side-effects.
if(healium_pp > healium_para_min && !breather.IsSleeping() && prob(30))
breather.Sleeping(rand(3 SECONDS, 5 SECONDS))
// Metabolize to reagent when concentration is high enough.
if(healium_pp > healium_sleep_min)
breather.reagents.add_reagent(/datum/reagent/healium, max(0, 1 - breather.reagents.get_reagent_amount(/datum/reagent/healium)))
/// Lose healium side effects
/obj/item/organ/lungs/proc/lose_healium(mob/living/carbon/breather, datum/gas_mixture/breath, old_healium_pp)
healium_euphoria = EUPHORIA_INACTIVE
/// Activates helium speech when partial pressure gets high enough
/obj/item/organ/lungs/proc/consume_helium(mob/living/carbon/breather, datum/gas_mixture/breath, helium_pp, old_helium_pp)
breathe_gas_volume(breath, /datum/gas/helium)
if(helium_pp > helium_speech_min)
if(old_helium_pp <= helium_speech_min)
RegisterSignal(breather, COMSIG_MOB_SAY, PROC_REF(handle_helium_speech))
else
if(old_helium_pp > helium_speech_min)
UnregisterSignal(breather, COMSIG_MOB_SAY)
/// Lose helium high pitched voice
/obj/item/organ/lungs/proc/lose_helium(mob/living/carbon/breather, datum/gas_mixture/breath, old_helium_pp)
UnregisterSignal(breather, COMSIG_MOB_SAY)
/// React to speach while hopped up on the high pitched voice juice
/obj/item/organ/lungs/proc/handle_helium_speech(mob/living/carbon/breather, list/speech_args)
SIGNAL_HANDLER
speech_args[SPEECH_SPANS] |= SPAN_SMALL_VOICE
/// Gain hypernob effects if we have enough of the stuff
/obj/item/organ/lungs/proc/consume_hypernoblium(mob/living/carbon/breather, datum/gas_mixture/breath, hypernob_pp, old_hypernob_pp)
breathe_gas_volume(breath, /datum/gas/hypernoblium)
if (hypernob_pp > gas_stimulation_min)
var/existing = breather.reagents.get_reagent_amount(/datum/reagent/hypernoblium)
breather.reagents.add_reagent(/datum/reagent/hypernoblium,max(0, 1 - existing))
/// Breathing in the stink gas
/obj/item/organ/lungs/proc/too_much_miasma(mob/living/carbon/breather, datum/gas_mixture/breath, miasma_pp, old_miasma_pp)
// Inhale Miasma. Exhale nothing.
breathe_gas_volume(breath, /datum/gas/miasma)
// Miasma side effects
if (HAS_TRAIT(breather, TRAIT_ANOSMIA)) //Anosmia quirk holder cannot smell miasma, but can get diseases from it.
return
switch(miasma_pp)
if(1 to 5) // BUBBER EDIT CHANGE: Original: 0.25 to 5
// At lower pp, give out a little warning
breather.clear_mood_event("smell")
if(prob(5))
to_chat(breather, span_notice("There is an unpleasant smell in the air."))
if(5 to 15)
//At somewhat higher pp, warning becomes more obvious
if(prob(15))
to_chat(breather, span_warning("You smell something horribly decayed inside this room."))
breather.add_mood_event("smell", /datum/mood_event/disgust/bad_smell)
if(15 to 30)
//Small chance to vomit. By now, people have internals on anyway
if(prob(5))
to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!"))
breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
breather.vomit(VOMIT_CATEGORY_DEFAULT)
if(30 to INFINITY)
//Higher chance to vomit. Let the horror start
if(prob(15))
to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!"))
breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
breather.vomit(VOMIT_CATEGORY_DEFAULT)
else
breather.clear_mood_event("smell")
// In a full miasma atmosphere with 101.34 pKa, about 10 disgust per breath, is pretty low compared to threshholds
// Then again, this is a purely hypothetical scenario and hardly reachable
breather.adjust_disgust(0.1 * miasma_pp)
/// We're free from the stick, clear out its impacts
/obj/item/organ/lungs/proc/safe_miasma(mob/living/carbon/breather, datum/gas_mixture/breath, old_miasma_pp)
// Clear out moods when immune to miasma, or if there's no miasma at all.
breather.clear_mood_event("smell")
/// Causes random euphoria and giggling. Large amounts knock you down
/obj/item/organ/lungs/proc/too_much_n2o(mob/living/carbon/breather, datum/gas_mixture/breath, n2o_pp, old_n2o_pp)
if(n2o_pp < n2o_para_min)
// Small amount of N2O, small side-effects.
if(n2o_pp <= n2o_detect_min)
if(old_n2o_pp > n2o_detect_min)
return BREATH_LOST
return
// No alert for small amounts, but the mob randomly feels euphoric.
if(old_n2o_pp >= n2o_para_min || old_n2o_pp <= n2o_detect_min)
breather.clear_alert(ALERT_TOO_MUCH_N2O)
if(prob(20))
n2o_euphoria = EUPHORIA_ACTIVE
breather.emote(pick("giggle", "laugh"))
breather.set_drugginess(30 SECONDS)
else
n2o_euphoria = EUPHORIA_INACTIVE
return
// More N2O, more severe side-effects. Causes stun/sleep.
if(old_n2o_pp < n2o_para_min)
if(!HAS_TRAIT(breather, TRAIT_ANOSMIA))
breather.throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o)
n2o_euphoria = EUPHORIA_ACTIVE
ADD_TRAIT(breather, TRAIT_ANALGESIA, OXYLOSS_TRAIT) // BUBBER EDIT ADDITION - N2O numbs pain
// give them one second of grace to wake up and run away a bit!
if(!HAS_TRAIT(breather, TRAIT_SLEEPIMMUNE))
breather.Unconscious(6 SECONDS)
// Enough to make the mob sleep.
if(n2o_pp > n2o_sleep_min)
breather.Sleeping(min(breather.AmountSleeping() + 100, 200))
/// N2O side-effects. "Too much N2O!"
/obj/item/organ/lungs/proc/safe_n2o(mob/living/carbon/breather, datum/gas_mixture/breath, old_n2o_pp)
n2o_euphoria = EUPHORIA_INACTIVE
breather.clear_alert(ALERT_TOO_MUCH_N2O)
REMOVE_TRAIT(breather, TRAIT_ANALGESIA, OXYLOSS_TRAIT) // BUBBER EDIT ADDITION - N2O numbs pain
// Breathe in nitrium. It's helpful, but has nasty side effects
/obj/item/organ/lungs/proc/too_much_nitrium(mob/living/carbon/breather, datum/gas_mixture/breath, nitrium_pp, old_nitrium_pp)
breathe_gas_volume(breath, /datum/gas/nitrium)
if(prob(20))
breather.emote("burp")
// Random chance to inflict side effects increases with pressure.
if((prob(nitrium_pp) && (nitrium_pp > 15)))
// Nitrium side-effect.
breather.adjustOrganLoss(ORGAN_SLOT_LUNGS, nitrium_pp * 0.1)
to_chat(breather, span_notice("You feel a burning sensation in your chest"))
// Metabolize to reagents.
if (nitrium_pp > 5)
var/existing = breather.reagents.get_reagent_amount(/datum/reagent/nitrium_low_metabolization)
breather.reagents.add_reagent(/datum/reagent/nitrium_low_metabolization, max(0, 2 - existing))
if (nitrium_pp > 10)
var/existing = breather.reagents.get_reagent_amount(/datum/reagent/nitrium_high_metabolization)
breather.reagents.add_reagent(/datum/reagent/nitrium_high_metabolization, max(0, 2 - existing))
/// Radioactive, green gas. Toxin damage, and a radiation chance
/obj/item/organ/lungs/proc/too_much_tritium(mob/living/carbon/breather, datum/gas_mixture/breath, trit_pp, old_trit_pp)
var/gas_breathed = breathe_gas_volume(breath, /datum/gas/tritium)
var/moles_visible = GLOB.meta_gas_info[/datum/gas/tritium][META_GAS_MOLES_VISIBLE] * BREATH_PERCENTAGE
// Tritium side-effects.
if(gas_breathed > moles_visible)
var/ratio = gas_breathed * 15
breather.adjustToxLoss(clamp(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE))
// If you're breathing in half an atmosphere of radioactive gas, you fucked up.
if((trit_pp > tritium_irradiation_moles_min) && SSradiation.can_irradiate_basic(breather))
var/lerp_scale = min(tritium_irradiation_moles_max, trit_pp - tritium_irradiation_moles_min) / (tritium_irradiation_moles_max - tritium_irradiation_moles_min)
var/chance = LERP(tritium_irradiation_probability_min, tritium_irradiation_probability_max, lerp_scale)
if (prob(chance))
breather.AddComponent(/datum/component/irradiated)
/// Really toxic stuff, very much trying to kill you
/obj/item/organ/lungs/proc/too_much_zauker(mob/living/carbon/breather, datum/gas_mixture/breath, zauker_pp, old_zauker_pp)
breathe_gas_volume(breath, /datum/gas/zauker)
// Metabolize to reagent.
if(zauker_pp > gas_stimulation_min)
var/existing = breather.reagents.get_reagent_amount(/datum/reagent/zauker)
breather.reagents.add_reagent(/datum/reagent/zauker, max(0, 1 - existing))
/**
* This proc tests if the lungs can breathe, if they can breathe a given gas mixture, and throws/clears gas alerts.
* It does this by calling subprocs "registered" to pay attention to different gas types
* There's also support for gases that should always be checked for, and procs that should run when a gas is finished
*
*
* Returns TRUE if the breath was successful, or FALSE if otherwise.
*
* Arguments:
* * breath: A gas mixture to test, or null.
* * breather: A carbon mob that is using the lungs to breathe.
*/
/obj/item/organ/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather)
if(HAS_TRAIT(breather, TRAIT_GODMODE))
breather.failed_last_breath = FALSE
breather.clear_alert(ALERT_NOT_ENOUGH_OXYGEN)
return FALSE
if(HAS_TRAIT(breather, TRAIT_NOBREATH))
return FALSE
// If the breath is falsy or "null", we can use the backup empty_breath.
if(!breath)
var/static/datum/gas_mixture/immutable/empty_breath = new(BREATH_VOLUME)
breath = empty_breath
// Indicates if there are moles of gas in the breath.
var/has_moles = breath.total_moles() != 0
// Check for moles of gas and handle partial pressures / special conditions.
if(has_moles)
// Breath has more than 0 moles of gas.
// Route gases through mask filter if breather is wearing one.
if(istype(breather.wear_mask) && (breather.wear_mask.clothing_flags & GAS_FILTERING) && breather.wear_mask.has_filter)
breath = breather.wear_mask.consume_filter(breath)
// Breath has 0 moles of gas, and we can breathe space
else if(HAS_TRAIT(src, TRAIT_SPACEBREATHING))
// The lungs can breathe anyways. What are you? Some bottom-feeding, scum-sucking algae eater?
breather.failed_last_breath = FALSE
// Vacuum-adapted lungs regenerate oxyloss even when breathing nothing.
if(breather.health >= breather.crit_threshold && breather.oxyloss)
breather.adjustOxyLoss(-5)
else
// Can't breathe!
breather.failed_last_breath = TRUE
// The list of gases in the breath.
var/list/breath_gases = breath.gases
// Copy the breath's temperature into breath_out to avoid cooling the output breath down unfairly
breath_out.temperature = breath.temperature
var/old_euphoria = (n2o_euphoria == EUPHORIA_ACTIVE || healium_euphoria == EUPHORIA_ACTIVE)
// Cache for sonic speed
var/list/last_partial_pressures = src.last_partial_pressures
var/list/breathe_always = src.breathe_always
var/list/breath_present = src.breath_present
var/list/breath_lost = src.breath_lost
// Build out our partial pressures, for use as we go
var/list/partial_pressures = list()
for(var/gas_id in breath_gases)
partial_pressures[gas_id] = breath.get_breath_partial_pressure(breath_gases[gas_id][MOLES])
// Treat gas as other types of gas
for(var/list/conversion_packet in treat_as)
var/read_from = conversion_packet[BREATH_RELATIONSHIP_INITIAL_GAS]
if(!partial_pressures[read_from])
continue
var/convert_into = conversion_packet[BREATH_RELATIONSHIP_CONVERT]
partial_pressures[convert_into] += partial_pressures[read_from] * conversion_packet[BREATH_RELATIONSHIP_MULTIPLIER]
if(partial_pressures[convert_into] <= 0)
partial_pressures -= convert_into // No negative values jeremy
// First, we breathe the stuff that always wants to be processed
// This is typically things like o2, stuff the mob needs to live
for(var/breath_id in breathe_always)
var/partial_pressure = partial_pressures[breath_id] || 0
var/old_partial_pressure = last_partial_pressures[breath_id] || 0
// Ensures the gas will always be instanciated, so people can interact with it safely
ASSERT_GAS(breath_id, breath)
var/inhale = breathe_always[breath_id]
call(src, inhale)(breather, breath, partial_pressure, old_partial_pressure)
// Now we'll handle the callbacks that want to be run conditionally off our current breath
for(var/breath_id in breath_gases)
var/when_present = breath_present[breath_id]
if(!when_present)
continue
var/reaction = call(src, when_present)(breather, breath, partial_pressures[breath_id], last_partial_pressures[breath_id])
if(reaction == BREATH_LOST)
var/on_lose = breath_lost[breath_id]
if(on_lose)
call(src, on_lose)(breather, breath, partial_pressures[breath_id], last_partial_pressures[breath_id])
// Finally, we'll run the callbacks that aren't in breath_gases, but WERE in our last breath
for(var/gas_lost in last_partial_pressures)
// If we still have it, go away
if(breath_gases[gas_lost])
continue
var/on_loss = breath_lost[gas_lost]
if(!on_loss)
continue
call(src, on_loss)(breather, breath, last_partial_pressures[gas_lost])
src.last_partial_pressures = partial_pressures
// Handle chemical euphoria mood event, caused by gases such as N2O or healium.
var/new_euphoria = (n2o_euphoria == EUPHORIA_ACTIVE || healium_euphoria == EUPHORIA_ACTIVE)
if (!old_euphoria && new_euphoria)
breather.add_mood_event("chemical_euphoria", /datum/mood_event/chemical_euphoria)
else if (old_euphoria && !new_euphoria)
breather.clear_mood_event("chemical_euphoria")
if(has_moles)
handle_breath_temperature(breath, breather)
// Merge breath_out into breath. They're kept seprerate before now to ensure stupid like, order of operations shit doesn't happen
// But that time has passed
breath.merge(breath_out)
// Resets immutable gas_mixture to empty.
breath_out.garbage_collect()
breath.garbage_collect()
// Returning FALSE indicates the breath failed.
if(!breather.failed_last_breath)
return TRUE
/// Remove gas from breath. If output_gas is given, transfers the removed gas to the lung's gas_mixture.
/// Removes 100% of the given gas type unless given a volume argument.
/// Returns the amount of gas theoretically removed.
/obj/item/organ/lungs/proc/breathe_gas_volume(datum/gas_mixture/breath, remove_id, exchange_id = null, volume = INFINITY)
var/list/breath_gases = breath.gases
volume = min(volume, breath_gases[remove_id][MOLES])
breath_gases[remove_id][MOLES] -= volume
if(exchange_id)
ASSERT_GAS(exchange_id, breath_out)
breath_out.gases[exchange_id][MOLES] += volume
return volume
/// Applies suffocation side-effects to a given Human, scaling based on ratio of required pressure VS "true" pressure.
/// If pressure is greater than 0, the return value will represent the amount of gas successfully breathed.
/obj/item/organ/lungs/proc/handle_suffocation(mob/living/carbon/human/suffocator = null, breath_pp = 0, safe_breath_min = 0, mole_count = 0)
. = 0
// Can't suffocate without a Human, or without minimum breath pressure.
if(!suffocator || !safe_breath_min)
return
// Mob is suffocating.
suffocator.failed_last_breath = TRUE
// Give them a chance to notice something is wrong.
if(prob(20))
suffocator.emote("gasp")
// If mob is at critical health, check if they can be damaged further.
if(suffocator.health < suffocator.crit_threshold)
// Mob is immune to damage at critical health.
if(HAS_TRAIT(suffocator, TRAIT_NOCRITDAMAGE))
return
// Reagents like Epinephrine stop suffocation at critical health.
if(suffocator.reagents.has_reagent(crit_stabilizing_reagent, needs_metabolizing = TRUE))
return
// Low pressure.
if(breath_pp)
var/ratio = safe_breath_min / breath_pp
suffocator.adjustOxyLoss(min(5 * ratio, HUMAN_MAX_OXYLOSS))
return mole_count * ratio / 6
// Zero pressure.
if(suffocator.health >= suffocator.crit_threshold)
suffocator.adjustOxyLoss(HUMAN_MAX_OXYLOSS)
else
suffocator.adjustOxyLoss(HUMAN_CRIT_MAX_OXYLOSS)
/obj/item/organ/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/breather) // called by human/life, handles temperatures
var/breath_temperature = breath.temperature
if(!HAS_TRAIT(breather, TRAIT_RESISTCOLD)) // COLD DAMAGE
var/cold_modifier = breather.dna.species.coldmod
var/breath_effect_prob = 0
if(breath_temperature < cold_level_3_threshold)
breather.apply_damage(cold_level_3_damage * cold_modifier, cold_damage_type, spread_damage = TRUE)
breath_effect_prob = 100
if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold)
breather.apply_damage(cold_level_2_damage * cold_modifier, cold_damage_type, spread_damage = TRUE)
breath_effect_prob = 50
if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold)
breather.apply_damage(cold_level_1_damage * cold_modifier, cold_damage_type, spread_damage = TRUE)
breath_effect_prob = 25
if(breath_temperature < cold_level_1_threshold)
if(prob(sqrt(breath_effect_prob) * 4))
to_chat(breather, span_warning("You feel [cold_message] in your [name]!"))
if(prob(50))
breather.emote("shiver")
if(prob(breath_effect_prob))
// Breathing into your mask, no particle. We can add fogged up glasses later
if(breather.is_mouth_covered())
return
// Even though breathing via internals TECHNICALLY exhales into the environment, we'll still block it
if(breather.internal || breather.external)
return
emit_breath_particle(breather, /particles/fog/breath)
if(!HAS_TRAIT(breather, TRAIT_RESISTHEAT)) // HEAT DAMAGE
var/heat_modifier = breather.dna.species.heatmod
var/heat_message_prob = 0
if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold)
breather.apply_damage(heat_level_1_damage * heat_modifier, heat_damage_type, spread_damage = TRUE)
heat_message_prob = 100
if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold)
breather.apply_damage(heat_level_2_damage * heat_modifier, heat_damage_type, spread_damage = TRUE)
heat_message_prob = 50
if(breath_temperature > heat_level_3_threshold)
breather.apply_damage(heat_level_3_damage * heat_modifier, heat_damage_type, spread_damage = TRUE)
heat_message_prob = 25
if(breath_temperature > heat_level_1_threshold)
if(prob(sqrt(heat_message_prob) * 4))
to_chat(breather, span_warning("You feel [hot_message] in your [name]!"))
// The air you breathe out should match your body temperature
breath.temperature = breather.bodytemperature
/// Creates a particle effect off the mouth of the passed mob.
/obj/item/organ/lungs/proc/emit_breath_particle(mob/living/carbon/human/breather, particle_type)
ASSERT(ispath(particle_type, /particles))
var/obj/effect/abstract/particle_holder/holder = new(breather, particle_type)
var/particles/breath_particle = holder.particles
var/breath_dir = breather.dir
var/list/particle_grav = list(0, 0.1, 0)
var/list/particle_pos = list(0, breather.mob_height + 2, 0)
if(breath_dir & NORTH)
particle_grav[2] = 0.2
breath_particle.rotation = pick(-45, 45)
// Layer it behind the mob since we're facing away from the camera
holder.pixel_w -= 4
holder.pixel_y += 4
if(breath_dir & WEST)
particle_grav[1] = -0.2
particle_pos[1] = -5
breath_particle.rotation = -45
if(breath_dir & EAST)
particle_grav[1] = 0.2
particle_pos[1] = 5
breath_particle.rotation = 45
if(breath_dir & SOUTH)
particle_grav[2] = 0.2
breath_particle.rotation = pick(-45, 45)
// Shouldn't be necessary but just for parity
holder.pixel_w += 4
holder.pixel_y -= 4
breath_particle.gravity = particle_grav
breath_particle.position = particle_pos
QDEL_IN(holder, breath_particle.lifespan)
/obj/item/organ/lungs/on_life(seconds_per_tick, times_fired)
. = ..()
if(failed && !(organ_flags & ORGAN_FAILING))
failed = FALSE
return
if(damage >= low_threshold)
var/do_i_cough = SPT_PROB((damage < high_threshold) ? 2.5 : 5, seconds_per_tick) // between : past high
if(do_i_cough)
owner.emote("cough")
if(organ_flags & ORGAN_FAILING && owner.stat == CONSCIOUS)
owner.visible_message(span_danger("[owner] grabs [owner.p_their()] throat, struggling for breath!"), span_userdanger("You suddenly feel like you can't breathe!"))
failed = TRUE
/obj/item/organ/lungs/get_availability(datum/species/owner_species, mob/living/owner_mob)
return owner_species.mutantlungs
/obj/item/organ/lungs/feel_for_damage(self_aware)
if(organ_flags & ORGAN_FAILING)
if(self_aware)
return span_boldwarning("Your lungs hurt madly[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and you can't breathe"]!")
return span_boldwarning("It hurts madly[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and you can't breathe"]!")
if(damage < low_threshold)
return ""
if(damage < high_threshold)
if(self_aware)
return span_warning("Your lungs feel tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and breathing is harder"].")
return span_warning("It feels tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and breathing is harder"].")
if(self_aware)
return span_boldwarning("Your lungs feel extremely tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and every breath is a struggle"].")
return span_boldwarning("It feels extremely tight[HAS_TRAIT(owner, TRAIT_NOBREATH) ? "" : ", and every breath is a struggle"].")
#define SMOKER_ORGAN_HEALTH (STANDARD_ORGAN_THRESHOLD * 0.75)
#define SMOKER_LUNG_HEALING (STANDARD_ORGAN_HEALING * 0.75)
/obj/item/organ/lungs/plasmaman
name = "plasma filter"
desc = "A spongy rib-shaped mass for filtering plasma from the air."
icon_state = "lungs-plasma"
organ_traits = list(TRAIT_NOHUNGER) // A fresh breakfast of plasma is a great start to any morning.
safe_oxygen_min = 0 //We don't breathe this
safe_plasma_min = 4 //We breathe THIS!
safe_plasma_max = 0
/obj/item/organ/lungs/plasmaman/plasmaman_smoker
name = "smoker plasma filter"
desc = "A plasma filter that look discolored, a result from smoking a lot."
icon_state = "lungs_plasma_smoker"
maxHealth = SMOKER_ORGAN_HEALTH
healing_factor = SMOKER_LUNG_HEALING
/obj/item/organ/lungs/slime
name = "slime vacuole"
desc = "A large organelle designed to store oxygen and other important gasses."
safe_plasma_max = 0 //We breathe this to gain POWER.
/obj/item/organ/lungs/slime/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather_slime)
. = ..()
if (breath?.gases[/datum/gas/plasma])
var/plasma_pp = breath.get_breath_partial_pressure(breath.gases[/datum/gas/plasma][MOLES])
breather_slime.blood_volume += (0.2 * plasma_pp) // 10/s when breathing literally nothing but plasma, which will suffocate you.
/obj/item/organ/lungs/smoker_lungs
name = "smoker lungs"
desc = "A pair of lungs that look sickly, a result from smoking a lot."
icon_state = "lungs_smoker"
maxHealth = SMOKER_ORGAN_HEALTH
healing_factor = SMOKER_LUNG_HEALING
/obj/item/organ/lungs/cybernetic
name = "basic cybernetic lungs"
desc = "A basic cybernetic version of the lungs found in traditional humanoid entities."
failing_desc = "seems to be broken."
icon_state = "lungs-c"
organ_flags = ORGAN_ROBOTIC
maxHealth = STANDARD_ORGAN_THRESHOLD * 0.5
var/emp_vulnerability = 80 //Chance of permanent effects if emp-ed.
/obj/item/organ/lungs/cybernetic/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people.
owner.losebreath += 20
COOLDOWN_START(src, severe_cooldown, 30 SECONDS)
if(prob(emp_vulnerability/severity)) //Chance of permanent effects
organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon.
/obj/item/organ/lungs/cybernetic/tier2
name = "cybernetic lungs"
desc = "A cybernetic version of the lungs found in traditional humanoid entities. Allows for greater intakes of oxygen than organic lungs, requiring slightly less pressure."
icon_state = "lungs-c-u"
maxHealth = 1.5 * STANDARD_ORGAN_THRESHOLD
safe_oxygen_min = 13
emp_vulnerability = 40
/obj/item/organ/lungs/cybernetic/tier3
name = "upgraded cybernetic lungs"
desc = "A more advanced version of the stock cybernetic lungs. Features the ability to filter out lower levels of plasma and carbon dioxide."
icon_state = "lungs-c-u2"
safe_plasma_max = 20
safe_co2_max = 20
maxHealth = 2 * STANDARD_ORGAN_THRESHOLD
safe_oxygen_min = 13
emp_vulnerability = 20
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
/obj/item/organ/lungs/cybernetic/surplus
name = "surplus prosthetic lungs"
desc = "Two fragile, inflatable sacks of air that only barely mimic the function of human lungs. \
Offer no protection against EMPs."
icon_state = "lungs-c-s"
maxHealth = 0.35 * STANDARD_ORGAN_THRESHOLD
emp_vulnerability = 100
//surplus organs are so awful that they explode when removed, unless failing
/obj/item/organ/lungs/cybernetic/surplus/Initialize(mapload)
. = ..()
AddElement(/datum/element/dangerous_organ_removal, /*surgical = */ TRUE)
/obj/item/organ/lungs/lavaland
name = "blackened frilled lungs" // blackened from necropolis exposure
desc = "Exposure to the necropolis has mutated these lungs to breathe the air of Indecipheres, the lava-covered moon."
icon_state = "lungs-ashwalker"
// Normal oxygen is 21 kPa partial pressure, but SS13 humans can tolerate down
// to 16 kPa. So it follows that ashwalkers, as humanoids, follow the same rules.
#define GAS_TOLERANCE 5
/obj/item/organ/lungs/lavaland/Initialize(mapload)
var/datum/gas_mixture/immutable/planetary/mix = SSair.planetary[LAVALAND_DEFAULT_ATMOS]
if(!mix?.total_moles()) // this typically means we didn't load lavaland, like if we're using the LOWMEMORYMODE define
return ..()
// Take a "breath" of the air
var/datum/gas_mixture/breath = mix.remove(mix.total_moles() * BREATH_PERCENTAGE)
var/list/breath_gases = breath.gases
breath.assert_gases(
/datum/gas/oxygen,
/datum/gas/plasma,
/datum/gas/carbon_dioxide,
/datum/gas/nitrogen,
/datum/gas/bz,
/datum/gas/miasma,
)
var/oxygen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES])
// var/nitrogen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) BUBBERSTATION CHANGE: ALLOWS ASHWALKERS TO BREATHE ON STATION
var/plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES])
var/carbon_dioxide_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES])
var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES])
var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES])
safe_oxygen_min = max(0, oxygen_pp - GAS_TOLERANCE)
/* BUBBERSTATION CHANGE START: ALLOWS ASHWALKERS TO BREATHE ON STATION
safe_nitro_min = max(0, nitrogen_pp - GAS_TOLERANCE)
safe_plasma_min = max(0, plasma_pp - GAS_TOLERANCE)
*/
safe_nitro_min = 0
safe_plasma_min = 0
//BUBBERSTATION CHANGE END
// Increase plasma tolerance based on amount in base air
safe_plasma_max += plasma_pp
// CO2 is always a waste gas, so none is required, but ashwalkers
// tolerate the base amount plus tolerance*2 (humans tolerate only 10 pp)
safe_co2_max = carbon_dioxide_pp + GAS_TOLERANCE * 2
// The lung tolerance against BZ is also increased the amount of BZ in the base air
BZ_trip_balls_min += bz_pp
BZ_brain_damage_min += bz_pp
// Lungs adapted to a high miasma atmosphere do not process it, and breathe it back out
if(miasma_pp)
suffers_miasma = FALSE
return ..()
#undef GAS_TOLERANCE
/obj/item/organ/lungs/ethereal
name = "aeration reticulum"
desc = "These exotic lungs seem crunchier than most."
icon_state = "lungs_ethereal"
heat_level_1_threshold = FIRE_MINIMUM_TEMPERATURE_TO_SPREAD // 150C or 433k, in line with ethereal max safe body temperature
heat_level_2_threshold = 473
heat_level_3_threshold = 1073
/obj/item/organ/lungs/ethereal/ethereal_smoker
name = "smoker aeration reticulum"
desc = "A pair of exotic lungs that look pale and sickly, a result from smoking a lot."
icon_state = "lungs_ethereal_smoker"
maxHealth = SMOKER_ORGAN_HEALTH
healing_factor = SMOKER_LUNG_HEALING
/obj/item/organ/lungs/ethereal/Initialize(mapload)
. = ..()
add_gas_reaction(/datum/gas/water_vapor, while_present = PROC_REF(consume_water))
/// H2O electrolysis
/obj/item/organ/lungs/ethereal/proc/consume_water(mob/living/carbon/breather, datum/gas_mixture/breath, h2o_pp, old_h2o_pp)
var/gas_breathed = breath.gases[/datum/gas/water_vapor][MOLES]
breath.gases[/datum/gas/water_vapor][MOLES] -= gas_breathed
breath_out.assert_gases(/datum/gas/oxygen, /datum/gas/hydrogen)
breath_out.gases[/datum/gas/oxygen][MOLES] += gas_breathed
breath_out.gases[/datum/gas/hydrogen][MOLES] += gas_breathed * 2
/obj/item/organ/lungs/pod
name = "pod vacuole"
desc = "A large organelle designed to store oxygen and other important gasses."
foodtype_flags = PODPERSON_ORGAN_FOODTYPES
color = COLOR_LIME
#undef BREATH_RELATIONSHIP_INITIAL_GAS
#undef BREATH_RELATIONSHIP_CONVERT
#undef BREATH_RELATIONSHIP_MULTIPLIER
#undef SMOKER_ORGAN_HEALTH
#undef SMOKER_LUNG_HEALING