diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index ff9a23654de..b53c771922d 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -467,6 +467,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_PRETENDER_ROYAL_METABOLISM "pretender_royal_metabolism" #define TRAIT_BALLMER_SCIENTIST "ballmer_scientist" +//LUNG TRAITS +/// Lungs always breathe normally when in vacuum/space. +#define TRAIT_SPACEBREATHING "spacebreathing" + /// This mob can strip other mobs. #define TRAIT_CAN_STRIP "can_strip" /// Can use the nuclear device's UI, regardless of a lack of hands diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index f2efc397317..6ddb782d84d 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -182,6 +182,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS, ), + /obj/item/organ/internal/lungs = list( + "TRAIT_SPACEBREATHING" = TRAIT_SPACEBREATHING, + ), /obj/item/organ/internal/liver = list( "TRAIT_LAW_ENFORCEMENT_METABOLISM" = TRAIT_LAW_ENFORCEMENT_METABOLISM, "TRAIT_CULINARY_METABOLISM" = TRAIT_CULINARY_METABOLISM, diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 57607b2604c..c55ded3c681 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -31,11 +31,14 @@ clear_alert(category) return .() else if(!severity || severity == thealert.severity) - if(thealert.timeout) - clear_alert(category) - return .() - else //no need to update + if(!thealert.timeout) + // No need to update existing alert return thealert + // Reset timeout of existing alert + var/timeout = initial(thealert.timeout) + addtimer(CALLBACK(src, PROC_REF(alert_timeout), thealert, category), timeout) + thealert.timeout = world.time + timeout - world.tick_lag + return thealert else thealert = new type() thealert.override_alerts = override @@ -117,6 +120,13 @@ //Gas alerts +// Gas alerts are continuously thrown/cleared by: +// * /obj/item/organ/internal/lungs/proc/check_breath() +// * /mob/living/carbon/check_breath() +// * /mob/living/carbon/human/check_breath() +// * /datum/element/atmos_requirements/proc/on_non_stasis_life() +// * /mob/living/simple_animal/handle_environment() + /atom/movable/screen/alert/not_enough_oxy name = "Choking (No O2)" desc = "You're not getting enough oxygen. Find some good air before you pass out! The box in your backpack has an oxygen tank and breath mask in it." diff --git a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm index 440213e4785..3cc4fca59e6 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -13,17 +13,20 @@ /datum/status_effect/organ_set_bonus/carp/enable_bonus() . = ..() - ADD_TRAIT(src, TRAIT_SPACEWALK, REF(src)) + ADD_TRAIT(owner, TRAIT_SPACEWALK, REF(src)) /datum/status_effect/organ_set_bonus/carp/disable_bonus() . = ..() - REMOVE_TRAIT(src, TRAIT_SPACEWALK, REF(src)) + REMOVE_TRAIT(owner, TRAIT_SPACEWALK, REF(src)) ///Carp lungs! You can breathe in space! Oh... you can't breathe on the station, you need low oxygen environments. +/// Inverts behavior of lungs. Bypasses suffocation due to space / lack of gas, but also allows Oxygen to suffocate. /obj/item/organ/internal/lungs/carp name = "mutated carp-lungs" desc = "Carp DNA infused into what was once some normal lungs." - safe_oxygen_min = 0 //we don't breathe this! + // Oxygen causes suffocation. + safe_oxygen_min = 0 + safe_oxygen_max = 15 icon = 'icons/obj/medical/organs/infuser_organs.dmi' icon_state = "lungs" @@ -34,6 +37,7 @@ . = ..() AddElement(/datum/element/noticable_organ, "neck has odd gills.", BODY_ZONE_HEAD) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) + ADD_TRAIT(src, TRAIT_SPACEBREATHING, REF(src)) ///occasionally sheds carp teeth, stronger melee (bite) attacks, but you can't cover your mouth anymore. /obj/item/organ/internal/tongue/carp @@ -49,6 +53,7 @@ /obj/item/organ/internal/tongue/carp/Initialize(mapload) . = ..() + AddElement(/datum/element/noticable_organ, "has big sharp teeth.", BODY_ZONE_PRECISE_MOUTH) AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/carp) /obj/item/organ/internal/tongue/carp/Insert(mob/living/carbon/tongue_owner, special, drop_if_replaced) diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index b1498fc48b1..a9cdfa05af9 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -54,8 +54,9 @@ ///This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks. var/failed_last_breath = FALSE - + /// Used in [carbon/proc/check_breath] and [lungs/proc/check_breath]] var/co2overloadtime = null + var/obj/item/food/meat/slab/type_of_meat = /obj/item/food/meat/slab var/gib_type = /obj/effect/decal/cleanable/blood/gibs diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 65917e66dab..4bb42acbcea 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -47,7 +47,7 @@ // BREATHING // /////////////// -//Start of a breath chain, calls breathe() +// Start of a breath chain, calls [carbon/proc/breathe()] /mob/living/carbon/handle_breathing(delta_time, times_fired) var/next_breath = 4 var/obj/item/organ/internal/lungs/L = getorganslot(ORGAN_SLOT_LUNGS) @@ -70,7 +70,7 @@ var/obj/location_as_object = loc location_as_object.handle_internal_lifeform(src,0) -//Second link in a breath chain, calls check_breath() +// Second link in a breath chain, calls [carbon/proc/check_breath()] /mob/living/carbon/proc/breathe(delta_time, times_fired) var/obj/item/organ/internal/lungs = getorganslot(ORGAN_SLOT_LUNGS) if(SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE) & COMSIG_CARBON_BLOCK_BREATH) @@ -130,145 +130,202 @@ return TRUE return FALSE - -//Third link in a breath chain, calls handle_breath_temperature() +/** + * This proc tests if the lungs can breathe, if the mob can breathe a given gas mixture, and throws/clears gas alerts. + * If there are moles of gas in the given gas mixture, side-effects may be applied/removed on the mob. + * This proc expects a lungs organ in order to breathe successfully, but does not defer any work to it. + * + * Returns TRUE if the breath was successful, or FALSE if otherwise. + * + * Arguments: + * * breath: A gas mixture to test, or null. + */ /mob/living/carbon/proc/check_breath(datum/gas_mixture/breath) + . = TRUE + if(status_flags & GODMODE) failed_last_breath = FALSE clear_alert(ALERT_NOT_ENOUGH_OXYGEN) - return FALSE + return + if(HAS_TRAIT(src, TRAIT_NOBREATH)) - return FALSE + return + + // Breath may be null, so use a fallback "empty breath" for convenience. + if(!breath) + /// Fallback "empty breath" for convenience. + var/static/datum/gas_mixture/immutable/empty_breath = new(BREATH_VOLUME) + breath = empty_breath + + // Ensure gas volumes are present. + breath.assert_gases(/datum/gas/bz, /datum/gas/carbon_dioxide, /datum/gas/freon, /datum/gas/plasma, /datum/gas/pluoxium, /datum/gas/miasma, /datum/gas/nitrous_oxide, /datum/gas/nitrium, /datum/gas/oxygen) + + /// The list of gases in the breath. + var/list/breath_gases = breath.gases + /// Indicates if there are moles of gas in the breath. + var/has_moles = breath.total_moles() != 0 var/obj/item/organ/internal/lungs = getorganslot(ORGAN_SLOT_LUNGS) - if(!lungs) + // Indicates if lungs can breathe without gas. + var/can_breathe_vacuum = FALSE + if(lungs) + // Breathing with lungs. + // Check for vacuum-adapted lungs. + can_breathe_vacuum = HAS_TRAIT(lungs, TRAIT_SPACEBREATHING) + else + // Lungs are missing! Can't breathe. + // Simulates breathing zero moles of gas. + has_moles = FALSE + // Extra damage, let God sort ’em out! adjustOxyLoss(2) - //CRIT - if(!breath || (breath.total_moles() == 0) || !lungs) - if(reagents.has_reagent(/datum/reagent/medicine/epinephrine, needs_metabolizing = TRUE) && lungs) - return FALSE - adjustOxyLoss(1) - - failed_last_breath = TRUE - throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) - return FALSE - - var/safe_oxy_min = 16 + /// Minimum O2 before suffocation. + var/safe_oxygen_min = 16 + /// Maximum CO2 before side-effects. var/safe_co2_max = 10 + /// Maximum Plasma before side-effects. var/safe_plas_max = 0.05 - var/SA_para_min = 1 - var/SA_sleep_min = 5 - var/oxygen_used = 0 - var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME + /// Maximum Pluoxum before side-effects. + var/gas_stimulation_min = 0.002 // For Pluoxium + // Vars for N2O induced euphoria, stun, and sleep. + var/n2o_euphoria = EUPHORIA_LAST_FLAG + var/n2o_para_min = 1 + var/n2o_sleep_min = 5 - var/list/breath_gases = breath.gases - breath.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz) - var/O2_partialpressure = (breath_gases[/datum/gas/oxygen][MOLES]/breath.total_moles())*breath_pressure - var/Plasma_partialpressure = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure - var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide][MOLES]/breath.total_moles())*breath_pressure + // Partial pressures in our breath + // Main gases. + var/pluoxium_pp = 0 + var/o2_pp = 0 + var/plasma_pp = 0 + var/co2_pp = 0 + // Trace gases ordered alphabetically. + var/bz_pp = 0 + var/freon_pp = 0 + var/n2o_pp = 0 + var/nitrium_pp = 0 + var/miasma_pp = 0 + // Check for moles of gas and handle partial pressures / special conditions. + if(has_moles) + // Breath has more than 0 moles of gas. + // Partial pressures of "main gases". + pluoxium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium][MOLES]) + o2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES] + (8 * pluoxium_pp)) + plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) + co2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) + // Partial pressures of "trace" gases. + bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) + freon_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/freon][MOLES]) + miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) + n2o_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrous_oxide][MOLES]) + nitrium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrium][MOLES]) - //OXYGEN - if(O2_partialpressure < safe_oxy_min) //Not enough oxygen - if(prob(20)) - emote("gasp") - if(O2_partialpressure > 0) - var/ratio = 1 - O2_partialpressure/safe_oxy_min - adjustOxyLoss(min(5*ratio, 3)) - failed_last_breath = TRUE - oxygen_used = breath_gases[/datum/gas/oxygen][MOLES]*ratio - else - adjustOxyLoss(3) - failed_last_breath = TRUE - throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) - - else //Enough oxygen + // Breath has 0 moles of gas. + else if(can_breathe_vacuum) + // The mob can breathe anyways. What are you? Some bottom-feeding, scum-sucking algae eater? failed_last_breath = FALSE + // Vacuum-adapted lungs regenerate oxyloss even when breathing nothing. if(health >= crit_threshold) adjustOxyLoss(-5) - oxygen_used = breath_gases[/datum/gas/oxygen][MOLES] + else + // Can't breathe! Lungs are missing, and/or breath is empty. + . = FALSE + failed_last_breath = TRUE + + //-- PLUOXIUM --// + // Behaves like Oxygen with 8X efficacy, but metabolizes into a reagent. + if(pluoxium_pp) + // Inhale Pluoxium. Exhale nothing. + breath_gases[/datum/gas/pluoxium][MOLES] = 0 + // Metabolize to reagent. + if(pluoxium_pp > gas_stimulation_min) + var/existing = reagents.get_reagent_amount(/datum/reagent/pluoxium) + reagents.add_reagent(/datum/reagent/pluoxium, max(0, 1 - existing)) + + //-- OXYGEN --// + // Carbons need only Oxygen to breathe properly. + var/oxygen_used = 0 + // Minimum Oxygen effects. "Too little oxygen!" + if(!can_breathe_vacuum && (o2_pp < safe_oxygen_min)) + // Breathe insufficient amount of O2. + oxygen_used = handle_suffocation(o2_pp, safe_oxygen_min, breath_gases[/datum/gas/oxygen][MOLES]) + throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) + else + // Enough oxygen to breathe. + failed_last_breath = FALSE clear_alert(ALERT_NOT_ENOUGH_OXYGEN) + if(o2_pp) + // Inhale O2. + oxygen_used = breath_gases[/datum/gas/oxygen][MOLES] + // Heal mob if not in crit. + if(health >= crit_threshold) + adjustOxyLoss(-5) + // Exhale equivalent amount of CO2. + if(o2_pp) + breath_gases[/datum/gas/oxygen][MOLES] -= oxygen_used + breath_gases[/datum/gas/carbon_dioxide][MOLES] += oxygen_used - breath_gases[/datum/gas/oxygen][MOLES] -= oxygen_used - breath_gases[/datum/gas/carbon_dioxide][MOLES] += oxygen_used - - //CARBON DIOXIDE - if(CO2_partialpressure > safe_co2_max) - if(!co2overloadtime) - co2overloadtime = world.time - else if(world.time - co2overloadtime > 120) - Unconscious(60) - adjustOxyLoss(3) - if(world.time - co2overloadtime > 300) - adjustOxyLoss(8) + //-- CARBON DIOXIDE --// + // Maximum CO2 effects. "Too much CO2!" + if(co2_pp > safe_co2_max) + // CO2 side-effects. + // Give the mob a chance to notice. if(prob(20)) emote("cough") - + // 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(!co2overloadtime) + co2overloadtime = world.time + else if((world.time - co2overloadtime) > 12 SECONDS) + throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) + Unconscious(6 SECONDS) + // Lets hurt em a little, let them know we mean business. + adjustOxyLoss(3) + // They've been in here 30s now, start to kill them for their own good! + if((world.time - co2overloadtime) > 30 SECONDS) + adjustOxyLoss(8) else + // Reset side-effects. co2overloadtime = 0 + clear_alert(ALERT_TOO_MUCH_CO2) - //PLASMA - if(Plasma_partialpressure > safe_plas_max) - var/ratio = (breath_gases[/datum/gas/plasma][MOLES]/safe_plas_max) * 10 + //-- PLASMA --// + // Maximum Plasma effects. "Too much Plasma!" + if(plasma_pp > safe_plas_max) + // Plasma side-effects. + var/ratio = (breath_gases[/datum/gas/plasma][MOLES] / safe_plas_max) * 10 adjustToxLoss(clamp(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE)) throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) else + // Reset side-effects. clear_alert(ALERT_TOO_MUCH_PLASMA) - //NITROUS OXIDE - if(breath_gases[/datum/gas/nitrous_oxide]) - var/SA_partialpressure = (breath_gases[/datum/gas/nitrous_oxide][MOLES]/breath.total_moles())*breath_pressure - if(SA_partialpressure > SA_para_min) - throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o) - clear_mood_event("chemical_euphoria") - Unconscious(60) - if(SA_partialpressure > SA_sleep_min) - Sleeping(max(AmountSleeping() + 40, 200)) - else if(SA_partialpressure > 0.01) - clear_alert(ALERT_TOO_MUCH_N2O) - if(prob(20)) - emote(pick("giggle","laugh")) - add_mood_event("chemical_euphoria", /datum/mood_event/chemical_euphoria) - else - clear_mood_event("chemical_euphoria") - clear_alert(ALERT_TOO_MUCH_N2O) - else - clear_mood_event("chemical_euphoria") - clear_alert(ALERT_TOO_MUCH_N2O) + //-- TRACES --// + // If there's some other funk in the air lets deal with it here. - //BZ (Facepunch port of their Agent B) - if(breath_gases[/datum/gas/bz]) - var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure - if(bz_partialpressure > 1) + //-- BZ --// + // (Facepunch port of their Agent B) + if(bz_pp) + if(bz_pp > 1) adjust_hallucinations(20 SECONDS) - else if(bz_partialpressure > 0.01) + else if(bz_pp > 0.01) adjust_hallucinations(10 SECONDS) - //NITRIUM - if(breath_gases[/datum/gas/nitrium]) - var/nitrium_partialpressure = (breath_gases[/datum/gas/nitrium][MOLES]/breath.total_moles())*breath_pressure - if(nitrium_partialpressure > 0.5) - adjustFireLoss(nitrium_partialpressure * 0.15) - if(nitrium_partialpressure > 5) - adjustToxLoss(nitrium_partialpressure * 0.05) + //-- FREON --// + if(freon_pp) + adjustFireLoss(freon_pp * 0.25) - //FREON - if(breath_gases[/datum/gas/freon]) - var/freon_partialpressure = (breath_gases[/datum/gas/freon][MOLES]/breath.total_moles())*breath_pressure - adjustFireLoss(freon_partialpressure * 0.25) - - //MIASMA - if(breath_gases[/datum/gas/miasma]) - var/miasma_partialpressure = (breath_gases[/datum/gas/miasma][MOLES]/breath.total_moles())*breath_pressure - - if(prob(1 * miasma_partialpressure)) - var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3) + //-- MIASMA --// + if(!miasma_pp) + // Clear moodlet if no miasma at all. + clear_mood_event("smell") + else + // Miasma sickness + if(prob(1 * miasma_pp)) + var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(max_symptoms = 2, max_level = 3) miasma_disease.name = "Unknown" - ForceContractDisease(miasma_disease, TRUE, TRUE) - - //Miasma side effects - switch(miasma_partialpressure) + ForceContractDisease(miasma_disease, make_copy = TRUE, del_on_fail = TRUE) + // Miasma side-effects. + switch(miasma_pp) if(0.25 to 5) // At lower pp, give out a little warning clear_mood_event("smell") @@ -294,16 +351,77 @@ else clear_mood_event("smell") - //Clear all moods if no miasma at all + //-- NITROUS OXIDE --// + if(n2o_pp > n2o_para_min) + // More N2O, more severe side-effects. Causes stun/sleep. + n2o_euphoria = EUPHORIA_ACTIVE + throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o) + // 60 gives them one second to wake up and run away a bit! + Unconscious(6 SECONDS) + // Enough to make the mob sleep. + if(n2o_pp > n2o_sleep_min) + Sleeping(max(AmountSleeping() + 40, 200)) + else if(n2o_pp > 0.01) + // No alert for small amounts, but the mob randomly feels euphoric. + if(prob(20)) + n2o_euphoria = EUPHORIA_ACTIVE + emote(pick("giggle","laugh")) + else + n2o_euphoria = EUPHORIA_INACTIVE else - clear_mood_event("smell") + // Reset side-effects, for zero or extremely small amounts of N2O. + n2o_euphoria = EUPHORIA_INACTIVE + clear_alert(ALERT_TOO_MUCH_N2O) + + //-- NITRIUM --// + if(nitrium_pp) + if(nitrium_pp > 0.5) + adjustFireLoss(nitrium_pp * 0.15) + if(nitrium_pp > 5) + adjustToxLoss(nitrium_pp * 0.05) + + // Handle chemical euphoria mood event, caused by N2O. + if (n2o_euphoria == EUPHORIA_ACTIVE) + add_mood_event("chemical_euphoria", /datum/mood_event/chemical_euphoria) + else if (n2o_euphoria == EUPHORIA_INACTIVE) + clear_mood_event("chemical_euphoria") + // Activate mood on first flag, remove on second, do nothing on third. + + if(has_moles) + handle_breath_temperature(breath) breath.garbage_collect() - //BREATH TEMPERATURE - handle_breath_temperature(breath) - - return TRUE +/// 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. +/mob/living/carbon/proc/handle_suffocation(breath_pp = 0, safe_breath_min = 0, true_pp = 0) + . = 0 + // Can't suffocate without minimum breath pressure. + if(!safe_breath_min) + return + // Mob is suffocating. + failed_last_breath = TRUE + // Give them a chance to notice something is wrong. + if(prob(20)) + emote("gasp") + // Mob is at critical health, check if they can be damaged further. + if(health < crit_threshold) + // Mob is immune to damage at critical health. + if(HAS_TRAIT(src, TRAIT_NOCRITDAMAGE)) + return + // Reagents like Epinephrine stop suffocation at critical health. + if(reagents.has_reagent(/datum/reagent/medicine/epinephrine, needs_metabolizing = TRUE)) + return + // Low pressure. + if(breath_pp) + var/ratio = safe_breath_min / breath_pp + adjustOxyLoss(min(5 * ratio, 3)) + return true_pp * ratio / 6 + // Zero pressure. + if(health >= crit_threshold) + adjustOxyLoss(3) + else + adjustOxyLoss(1) /// Fourth and final link in a breath chain /mob/living/carbon/proc/handle_breath_temperature(datum/gas_mixture/breath) diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 0fd96f30abd..5015f3abd04 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -24,7 +24,6 @@ //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 @@ -38,7 +37,7 @@ 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 //nitrium and Freon + 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 @@ -97,271 +96,317 @@ receiver.clear_alert(ALERT_NOT_ENOUGH_N2O) return ..() +/** + * This proc tests if the lungs can breathe, if they can breathe a given gas mixture, and throws/clears gas alerts. + * If there are moles of gas in the given gas mixture, side-effects may be applied/removed on the mob. + * If a required gas (such as Oxygen) is missing from the breath, then it calls [proc/handle_suffocation]. + * + * 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/internal/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/breather) + . = TRUE + if(breather.status_flags & GODMODE) - breather.failed_last_breath = FALSE //clear oxy issues + breather.failed_last_breath = FALSE breather.clear_alert(ALERT_NOT_ENOUGH_OXYGEN) return + if(HAS_TRAIT(breather, TRAIT_NOBREATH)) return - if(!breath || (breath.total_moles() == 0)) - if(breather.reagents.has_reagent(crit_stabilizing_reagent, needs_metabolizing = TRUE)) - return - if(breather.health >= breather.crit_threshold) - breather.adjustOxyLoss(HUMAN_MAX_OXYLOSS) - else if(!HAS_TRAIT(breather, TRAIT_NOCRITDAMAGE)) - breather.adjustOxyLoss(HUMAN_CRIT_MAX_OXYLOSS) - - breather.failed_last_breath = TRUE - if(safe_oxygen_min) - breather.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) - else if(safe_plasma_min) - breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas) - else if(safe_co2_min) - breather.throw_alert(ALERT_NOT_ENOUGH_CO2, /atom/movable/screen/alert/not_enough_co2) - else if(safe_nitro_min) - breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro) - return FALSE + // Breath may be null, so use a fallback "empty breath" for convenience. + if(!breath) + /// Fallback "empty breath" for convenience. + var/static/datum/gas_mixture/immutable/empty_breath = new(BREATH_VOLUME) + breath = empty_breath + // Ensure gas volumes are present. for(var/gas_id in GLOB.meta_gas_info) breath.assert_gas(gas_id) - if(istype(breather.wear_mask) && (breather.wear_mask.clothing_flags & GAS_FILTERING) && breather.wear_mask.has_filter) - breath = breather.wear_mask.consume_filter(breath) - - var/gas_breathed = 0 + // Indicates if there are moles of gas in the breath. + var/has_moles = breath.total_moles() != 0 + // The list of gases in the breath. var/list/breath_gases = breath.gases - //Partial pressures in our breath - var/O2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES])+(8*breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium][MOLES])) - var/N2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) - var/Plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) - var/CO2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) - //Vars for n2o and healium induced euphorias. + // Indicates if lungs can breathe without gas. + var/can_breathe_vacuum = HAS_TRAIT(src, TRAIT_SPACEBREATHING) + // Re-usable var used to remove a limited volume of each gas from the given gas mixture. + var/gas_breathed = 0 + // Vars for N2O/healium induced euphoria, stun, and sleep. var/n2o_euphoria = EUPHORIA_LAST_FLAG var/healium_euphoria = EUPHORIA_LAST_FLAG - //Handle subtypes' breath processing - handle_gas_override(breather,breath_gases, gas_breathed) + // Partial pressures in the breath. + // Main Gases + var/pluoxium_pp = 0 + var/o2_pp = 0 + var/n2_pp = 0 + var/co2_pp = 0 + var/plasma_pp = 0 + // Trace Gases, ordered alphabetically. + var/bz_pp = 0 + var/freon_pp = 0 + var/healium_pp = 0 + var/helium_pp = 0 + var/halon_pp = 0 + var/hypernob_pp = 0 + var/miasma_pp = 0 + var/n2o_pp = 0 + var/nitrium_pp = 0 + var/trit_pp = 0 + var/zauker_pp = 0 - //-- OXY --// + // 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_gases = breath.gases + // Partial pressures of "main" gases. + pluoxium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium][MOLES]) + o2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES]) + (8 * pluoxium_pp) + n2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) + co2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) + plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) + // Partial pressures of "trace" gases. + bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) + freon_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/freon][MOLES]) + halon_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/halon][MOLES]) + healium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/healium][MOLES]) + helium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/helium][MOLES]) + hypernob_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/hypernoblium][MOLES]) + miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) + n2o_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrous_oxide][MOLES]) + nitrium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrium][MOLES]) + trit_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/tritium][MOLES]) + zauker_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/zauker][MOLES]) - //Too much oxygen! //Yes, some species may not like it. + // Breath has 0 moles of gas. + else if(can_breathe_vacuum) + // 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.adjustOxyLoss(-5) + else + // Can't breathe! + . = FALSE + breather.failed_last_breath = TRUE + + // Handle subtypes' breath processing + handle_gas_override(breather, breath_gases, 0) + + //-- MAIN GASES --// + + //-- PLUOXIUM --// + // Behaves like Oxygen with 8X efficacy, but metabolizes into a reagent. + if(pluoxium_pp) + // Inhale Pluoxium. Exhale nothing. + breathe_gas_volume(breath_gases, /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)) + + //-- OXYGEN --// + // Maximum Oxygen effects. "Too much O2!" + // If too much Oxygen is poisonous. if(safe_oxygen_max) - if(O2_pp > safe_oxygen_max) - var/ratio = (breath_gases[/datum/gas/oxygen][MOLES]/safe_oxygen_max) * 10 + if(o2_pp && (o2_pp > safe_oxygen_max)) + // O2 side-effects. + var/ratio = (breath_gases[/datum/gas/oxygen][MOLES] / safe_oxygen_max) * 10 breather.apply_damage_type(clamp(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type) breather.throw_alert(ALERT_TOO_MUCH_OXYGEN, /atom/movable/screen/alert/too_much_oxy) else + // Reset side-effects. breather.clear_alert(ALERT_TOO_MUCH_OXYGEN) - //Too little oxygen! + // Minimum Oxygen effects. + // If the lungs need Oxygen to breathe properly, O2 is exchanged with CO2. if(safe_oxygen_min) - if(O2_pp < safe_oxygen_min) - gas_breathed = handle_too_little_breath(breather, O2_pp, safe_oxygen_min, breath_gases[/datum/gas/oxygen][MOLES]) + // Suffocation side-effects. + if(!can_breathe_vacuum && (o2_pp < safe_oxygen_min)) breather.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) + // Inhale insufficient amount of O2, exhale CO2. + if(o2_pp) + gas_breathed = handle_suffocation(breather, o2_pp, safe_oxygen_min, breath_gases[/datum/gas/oxygen][MOLES]) + breathe_gas_volume(breath_gases, /datum/gas/oxygen, /datum/gas/carbon_dioxide, volume = gas_breathed) else + // Enough oxygen to breathe. breather.failed_last_breath = FALSE - if(breather.health >= breather.crit_threshold) - breather.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/oxygen][MOLES] breather.clear_alert(ALERT_NOT_ENOUGH_OXYGEN) + // Inhale Oxygen, exhale equivalent amount of CO2. + if(o2_pp) + breathe_gas_volume(breath_gases, /datum/gas/oxygen, /datum/gas/carbon_dioxide) + // Heal mob if not in crit. + if(breather.health >= breather.crit_threshold) + breather.adjustOxyLoss(-5) - //Exhale - breath_gases[/datum/gas/oxygen][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed - gas_breathed = 0 - - //-- Nitrogen --// - - //Too much nitrogen! + //-- NITROGEN --// + // Maximum Nitrogen effects. "Too much N2!" if(safe_nitro_max) - if(N2_pp > safe_nitro_max) + if(n2_pp && (n2_pp > safe_nitro_max)) + // N2 side-effects. var/ratio = (breath_gases[/datum/gas/nitrogen][MOLES]/safe_nitro_max) * 10 breather.apply_damage_type(clamp(ratio, nitro_breath_dam_min, nitro_breath_dam_max), nitro_damage_type) breather.throw_alert(ALERT_TOO_MUCH_NITRO, /atom/movable/screen/alert/too_much_nitro) else + // Reset side-effects. breather.clear_alert(ALERT_TOO_MUCH_NITRO) - //Too little nitrogen! + // Minimum Nitrogen effects. + // If the lungs need Nitrogen to breathe properly, N2 is exchanged with CO2. if(safe_nitro_min) - if(N2_pp < safe_nitro_min) - gas_breathed = handle_too_little_breath(breather, N2_pp, safe_nitro_min, breath_gases[/datum/gas/nitrogen][MOLES]) + // Suffocation side-effects. + if(!can_breathe_vacuum && (n2_pp < safe_nitro_min)) breather.throw_alert(ALERT_NOT_ENOUGH_NITRO, /atom/movable/screen/alert/not_enough_nitro) + // Inhale insufficient amount of N2, exhale CO2. + if(n2_pp) + gas_breathed = handle_suffocation(breather, n2_pp, safe_nitro_min, breath_gases[/datum/gas/nitrogen][MOLES]) + breathe_gas_volume(breath_gases, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, volume = gas_breathed) else + // Enough nitrogen to breathe. breather.failed_last_breath = FALSE - if(breather.health >= breather.crit_threshold) - breather.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/nitrogen][MOLES] breather.clear_alert(ALERT_NOT_ENOUGH_NITRO) + // Inhale N2, exhale equivalent amount of CO2. Look ma, sideways breathing! + if(n2_pp) + breathe_gas_volume(breath_gases, /datum/gas/nitrogen, /datum/gas/carbon_dioxide) + // Heal mob if not in crit. + if(breather.health >= breather.crit_threshold) + breather.adjustOxyLoss(-5) - //Exhale - breath_gases[/datum/gas/nitrogen][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed - gas_breathed = 0 - - //-- CO2 --// - - //CO2 does not affect failed_last_breath. So if there was enough oxygen in the air but too much co2, this will hurt you, but only once per 4 ticks, instead of once per tick. + //-- CARBON DIOXIDE --// + // Maximum CO2 effects. "Too much CO2!" if(safe_co2_max) - if(CO2_pp > safe_co2_max) - if(!breather.co2overloadtime) // 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. - breather.co2overloadtime = world.time - else if(world.time - breather.co2overloadtime > 120) - breather.Unconscious(60) - breather.apply_damage_type(3, co2_damage_type) // Lets hurt em a little, let them know we mean business - if(world.time - breather.co2overloadtime > 300) // They've been in here 30s now, lets start to kill them for their own good! - breather.apply_damage_type(8, co2_damage_type) - breather.throw_alert(ALERT_TOO_MUCH_CO2, /atom/movable/screen/alert/too_much_co2) - if(prob(20)) // Lets give them some chance to know somethings not right though I guess. + if(co2_pp && (co2_pp > safe_co2_max)) + // CO2 side-effects. + // Give the mob a chance to notice. + if(prob(20)) breather.emote("cough") - + // 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(!breather.co2overloadtime) + breather.co2overloadtime = world.time + else if((world.time - breather.co2overloadtime) > 12 SECONDS) + 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_type(3, co2_damage_type) + // 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_type(8, co2_damage_type) else + // Reset side-effects. breather.co2overloadtime = 0 breather.clear_alert(ALERT_TOO_MUCH_CO2) - //Too little CO2! + // Minimum CO2 effects. + // If the lungs need CO2 to breathe properly, CO2 is exchanged with O2. if(safe_co2_min) - if(CO2_pp < safe_co2_min) - gas_breathed = handle_too_little_breath(breather, CO2_pp, safe_co2_min, breath_gases[/datum/gas/carbon_dioxide][MOLES]) + // Suffocation side-effects. + if(!can_breathe_vacuum && (co2_pp < safe_co2_min)) breather.throw_alert(ALERT_NOT_ENOUGH_CO2, /atom/movable/screen/alert/not_enough_co2) + // Inhale insufficient amount of CO2, exhale O2. + if(co2_pp) + gas_breathed = handle_suffocation(breather, co2_pp, safe_co2_min, breath_gases[/datum/gas/carbon_dioxide][MOLES]) + breathe_gas_volume(breath_gases, /datum/gas/carbon_dioxide, /datum/gas/oxygen, volume = gas_breathed) else + // Enough CO2 to breathe. breather.failed_last_breath = FALSE - if(breather.health >= breather.crit_threshold) - breather.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/carbon_dioxide][MOLES] breather.clear_alert(ALERT_NOT_ENOUGH_CO2) - - //Exhale - breath_gases[/datum/gas/carbon_dioxide][MOLES] -= gas_breathed - breath_gases[/datum/gas/oxygen][MOLES] += gas_breathed - gas_breathed = 0 - + // Inhale CO2, exhale equivalent amount of O2. Look ma, reverse breathing! + if(co2_pp) + breathe_gas_volume(breath_gases, /datum/gas/carbon_dioxide, /datum/gas/oxygen) + // Heal mob if not in crit. + if(breather.health >= breather.crit_threshold) + breather.adjustOxyLoss(-5) //-- PLASMA --// - - //Too much plasma! + // Maximum Plasma effects. "Too much Plasma!" if(safe_plasma_max) - if(Plasma_pp > safe_plasma_max) - var/ratio = (breath_gases[/datum/gas/plasma][MOLES]/safe_plasma_max) * 10 + if(plasma_pp && (plasma_pp > safe_plasma_max)) + // Plasma side-effects. + var/ratio = (breath_gases[/datum/gas/plasma][MOLES] / safe_plasma_max) * 10 breather.apply_damage_type(clamp(ratio, plas_breath_dam_min, plas_breath_dam_max), plas_damage_type) breather.throw_alert(ALERT_TOO_MUCH_PLASMA, /atom/movable/screen/alert/too_much_plas) else + // Reset side-effects. breather.clear_alert(ALERT_TOO_MUCH_PLASMA) - - //Too little plasma! + // Minimum Plasma effects. + // If the lungs need Plasma to breathe properly, Plasma is exchanged with CO2. if(safe_plasma_min) - if(Plasma_pp < safe_plasma_min) - gas_breathed = handle_too_little_breath(breather, Plasma_pp, safe_plasma_min, breath_gases[/datum/gas/plasma][MOLES]) + // Suffocation side-effects. + if(!can_breathe_vacuum && (plasma_pp < safe_plasma_min)) breather.throw_alert(ALERT_NOT_ENOUGH_PLASMA, /atom/movable/screen/alert/not_enough_plas) + // Breathe insufficient amount of Plasma, exhale CO2. + if(plasma_pp) + gas_breathed = handle_suffocation(breather, plasma_pp, safe_plasma_min, breath_gases[/datum/gas/plasma][MOLES]) + breathe_gas_volume(breath_gases, /datum/gas/plasma, /datum/gas/carbon_dioxide, volume = gas_breathed) else + // Enough Plasma to breathe. breather.failed_last_breath = FALSE - if(breather.health >= breather.crit_threshold) - breather.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/plasma][MOLES] breather.clear_alert(ALERT_NOT_ENOUGH_PLASMA) - - //Exhale - breath_gases[/datum/gas/plasma][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed - gas_breathed = 0 + // Inhale Plasma, exhale equivalent amount of CO2. + if(plasma_pp) + breathe_gas_volume(breath_gases, /datum/gas/plasma, /datum/gas/carbon_dioxide) + // Heal mob if not in crit. + if(breather.health >= breather.crit_threshold) + breather.adjustOxyLoss(-5) //-- TRACES --// + // If there's some other shit in the air lets deal with it here. - if(breath) // If there's some other shit in the air lets deal with it here. - - // Pluoxium - var/pluoxium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium][MOLES]) - 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)) - gas_breathed = breath_gases[/datum/gas/pluoxium][MOLES] - breath_gases[/datum/gas/pluoxium][MOLES] -= gas_breathed - - // N2O - - var/n2o_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrous_oxide][MOLES]) - if(n2o_pp > n2o_para_min) // Enough to make us stunned for a bit - breather.throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o) - breather.Unconscious(60) // 60 gives them one second to wake up and run away a bit! - if(n2o_pp > n2o_sleep_min) // Enough to make us sleep as well - breather.Sleeping(min(breather.AmountSleeping() + 100, 200)) - else if(n2o_pp > 0.01) // There is sleeping gas in their lungs, but only a little, so give them a bit of a warning - breather.clear_alert(ALERT_TOO_MUCH_N2O) - if(prob(20)) - n2o_euphoria = EUPHORIA_ACTIVE - breather.emote(pick("giggle", "laugh")) - else - n2o_euphoria = EUPHORIA_INACTIVE - breather.clear_alert(ALERT_TOO_MUCH_N2O) - - - // BZ - - var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) + //-- BZ --// + if(bz_pp) if(bz_pp > BZ_trip_balls_min) breather.adjust_hallucinations(20 SECONDS) - breather.reagents.add_reagent(/datum/reagent/bz_metabolites,5) + breather.reagents.add_reagent(/datum/reagent/bz_metabolites, 5) if(bz_pp > BZ_brain_damage_min && prob(33)) - breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) + breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150, ORGAN_ORGANIC) - // Tritium - var/trit_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/tritium][MOLES]) - // 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) - - gas_breathed = breath_gases[/datum/gas/tritium][MOLES] - - if (trit_pp > 0) - var/ratio = gas_breathed * 15 - breather.adjustToxLoss(clamp(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE)) - - breath_gases[/datum/gas/tritium][MOLES] -= gas_breathed - - // Nitrium - var/nitrium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrium][MOLES]) - if (prob(nitrium_pp) && nitrium_pp > 15) - breather.adjustOrganLoss(ORGAN_SLOT_LUNGS, nitrium_pp * 0.1) - to_chat(breather, "You feel a burning sensation in your chest") - gas_breathed = breath_gases[/datum/gas/nitrium][MOLES] - 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, 1 - existing)) - - breath_gases[/datum/gas/nitrium][MOLES] -= gas_breathed - - // Freon - var/freon_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/freon][MOLES]) + //-- FREON --// + if(freon_pp) + // Inhale Freon. Exhale nothing. + breathe_gas_volume(breath_gases, /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) + if (freon_pp > 40) breather.emote("gasp") breather.adjustFireLoss(15) - if (prob(freon_pp/2)) + 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) - gas_breathed = breath_gases[/datum/gas/freon][MOLES] - if (gas_breathed > gas_stimulation_min) - breather.reagents.add_reagent(/datum/reagent/freon,1) + breather.adjustFireLoss(freon_pp / 4) - breath_gases[/datum/gas/freon][MOLES]-=gas_breathed + //-- HALON --// + if(halon_pp) + // Inhale Halon. Exhale nothing. + breathe_gas_volume(breath_gases, /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))) - // Healium - var/healium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/healium][MOLES]) + //-- HEALIUM --// + // Sleeping gas with healing properties. + if(!healium_pp) + // Reset side-effects. + healium_euphoria = EUPHORIA_INACTIVE + else + // Inhale Healium. Exhale nothing. + breathe_gas_volume(breath_gases, /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!")) @@ -369,131 +414,207 @@ breather.emote("gasp") else healium_euphoria = EUPHORIA_INACTIVE - + // Stun/Sleep side-effects. if(healium_pp > healium_para_min) - breather.Unconscious(rand(30, 50))//not in seconds to have a much higher variation - if(healium_pp > healium_sleep_min) - var/existing = breather.reagents.get_reagent_amount(/datum/reagent/healium) - breather.reagents.add_reagent(/datum/reagent/healium,max(0, 1 - existing)) - gas_breathed = breath_gases[/datum/gas/healium][MOLES] - breath_gases[/datum/gas/healium][MOLES]-=gas_breathed + // Random chance to stun mob. Timing not in seconds to have a much higher variation + breather.Unconscious(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))) - // Proto Nitrate - // Inert - // Zauker - var/zauker_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/zauker][MOLES]) + //-- HELIUM --// + // Activates helium speech when partial pressure gets high enough + if(!helium_pp) + helium_speech = FALSE + UnregisterSignal(owner, COMSIG_MOB_SAY) + else + // Inhale Helium. Exhale nothing. + breathe_gas_volume(breath_gases, /datum/gas/helium) + // Helium side-effects. + if(helium_speech && (helium_pp <= helium_speech_min)) + helium_speech = FALSE + UnregisterSignal(owner, COMSIG_MOB_SAY) + else if(!helium_speech && (helium_pp > helium_speech_min)) + helium_speech = TRUE + RegisterSignal(owner, COMSIG_MOB_SAY, PROC_REF(handle_helium_speech)) + + //-- HYPER-NOBILUM --// + if(hypernob_pp) + // Inhale Hyber-Nobilum. Exhale nothing. + breathe_gas_volume(breath_gases, /datum/gas/hypernoblium) + // Metabolize to reagent. + 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)) + + //-- MIASMA --// + if(!miasma_pp || !suffers_miasma) + // Clear out moods when immune to miasma, or if there's no miasma at all. + owner.clear_mood_event("smell") + else + // Inhale Miasma. Exhale nothing. + breathe_gas_volume(breath_gases, /datum/gas/miasma) + // Miasma sickness + if(prob(0.5 * miasma_pp)) + var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(max_symptoms = min(round(max(miasma_pp / 2, 1), 1), 6), max_level = min(round(max(miasma_pp, 1), 1), 8)) + // tl;dr the first argument chooses the smaller of miasma_pp/2 or 6(typical max virus symptoms), the second chooses the smaller of miasma_pp or 8(max virus symptom level) + // Each argument has a minimum of 1 and rounds to the nearest value. Feel free to change the pp scaling I couldn't decide on good numbers for it. + miasma_disease.name = "Unknown" + miasma_disease.try_infect(owner) + // Miasma side effects + switch(miasma_pp) + if(0.25 to 5) + // At lower pp, give out a little warning + owner.clear_mood_event("smell") + if(prob(5)) + to_chat(owner, 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(owner, span_warning("You smell something horribly decayed inside this room.")) + owner.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(owner, span_warning("The stench of rotting carcasses is unbearable!")) + owner.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) + owner.vomit() + if(30 to INFINITY) + //Higher chance to vomit. Let the horror start + if(prob(15)) + to_chat(owner, span_warning("The stench of rotting carcasses is unbearable!")) + owner.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) + owner.vomit() + else + owner.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 + owner.adjust_disgust(0.1 * miasma_pp) + + //-- N2O --// + // N2O side-effects. "Too much N2O!" + // Small amount of N2O, small side-effects. Causes random euphoria and giggling. + if (n2o_pp > n2o_para_min) + // More N2O, more severe side-effects. Causes stun/sleep. + n2o_euphoria = EUPHORIA_ACTIVE + breather.throw_alert(ALERT_TOO_MUCH_N2O, /atom/movable/screen/alert/too_much_n2o) + // 60 gives them one second to wake up and run away a bit! + breather.Unconscious(6 SECONDS) + // Enough to make the mob sleep. + if(n2o_pp > n2o_sleep_min) + breather.Sleeping(min(breather.AmountSleeping() + 100, 200)) + else if(n2o_pp > 0.01) + // No alert for small amounts, but the mob randomly feels euphoric. + breather.clear_alert(ALERT_TOO_MUCH_N2O) + if(prob(20)) + n2o_euphoria = EUPHORIA_ACTIVE + breather.emote(pick("giggle", "laugh")) + else + n2o_euphoria = EUPHORIA_INACTIVE + else + // Reset side-effects, for zero or extremely small amounts of N2O. + n2o_euphoria = EUPHORIA_INACTIVE + breather.clear_alert(ALERT_TOO_MUCH_N2O) + + //-- NITRIUM --// + if (nitrium_pp) + // Inhale Nitrium. Exhale nothing. + breathe_gas_volume(breath_gases, /datum/gas/nitrium) + // 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, "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, 1 - existing)) + + //-- PROTO-NITRATE --// + // Inert + + //-- TRITIUM --// + if (trit_pp) + // Inhale Tritium. Exhale nothing. + gas_breathed = breathe_gas_volume(breath_gases, /datum/gas/tritium) + // Tritium side-effects. + 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) + + //-- ZAUKER --// + if(zauker_pp) + // Inhale Zauker. Exhale nothing. + breathe_gas_volume(breath_gases, /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)) - gas_breathed = breath_gases[/datum/gas/zauker][MOLES] - breath_gases[/datum/gas/zauker][MOLES]-=gas_breathed - // Halon - var/halon_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/halon][MOLES]) - if(halon_pp > gas_stimulation_min) - breather.adjustOxyLoss(5) - var/existing = breather.reagents.get_reagent_amount(/datum/reagent/halon) - breather.reagents.add_reagent(/datum/reagent/halon,max(0, 1 - existing)) - gas_breathed = breath_gases[/datum/gas/halon][MOLES] - breath_gases[/datum/gas/halon][MOLES]-=gas_breathed - - // Hyper-Nob - gas_breathed = breath_gases[/datum/gas/hypernoblium][MOLES] - if (gas_breathed > gas_stimulation_min) - var/existing = breather.reagents.get_reagent_amount(/datum/reagent/hypernoblium) - breather.reagents.add_reagent(/datum/reagent/hypernoblium,max(0, 1 - existing)) - breath_gases[/datum/gas/hypernoblium][MOLES]-=gas_breathed - - // Helium - //Activates helium speech when partial pressure gets high enough - var/helium_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/helium][MOLES]) - if(helium_pp > helium_speech_min && !helium_speech) - helium_speech = TRUE - RegisterSignal(owner, COMSIG_MOB_SAY, PROC_REF(handle_helium_speech)) - else if(helium_pp <= helium_speech_min && helium_speech) - helium_speech = FALSE - UnregisterSignal(owner, COMSIG_MOB_SAY) - gas_breathed = breath_gases[/datum/gas/helium][MOLES] - breath_gases[/datum/gas/helium][MOLES] -= gas_breathed - - // Miasma - if (breath_gases[/datum/gas/miasma] && suffers_miasma) - var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) - - //Miasma sickness - if(prob(0.5 * miasma_pp)) - var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(min(round(max(miasma_pp/2, 1), 1), 6), min(round(max(miasma_pp, 1), 1), 8)) - //tl;dr the first argument chooses the smaller of miasma_pp/2 or 6(typical max virus symptoms), the second chooses the smaller of miasma_pp or 8(max virus symptom level) // - miasma_disease.name = "Unknown"//^each argument has a minimum of 1 and rounds to the nearest value. Feel free to change the pp scaling I couldn't decide on good numbers for it. - miasma_disease.try_infect(owner) - - // Miasma side effects - switch(miasma_pp) - if(0.25 to 5) - // At lower pp, give out a little warning - owner.clear_mood_event("smell") - if(prob(5)) - to_chat(owner, 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(owner, span_warning("You smell something horribly decayed inside this room.")) - owner.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(owner, span_warning("The stench of rotting carcasses is unbearable!")) - owner.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) - owner.vomit() - if(30 to INFINITY) - //Higher chance to vomit. Let the horror start - if(prob(15)) - to_chat(owner, span_warning("The stench of rotting carcasses is unbearable!")) - owner.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench) - owner.vomit() - else - owner.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 - owner.adjust_disgust(0.1 * miasma_pp) - - breath_gases[/datum/gas/miasma][MOLES]-=gas_breathed - - // Clear out moods when no miasma at all - else - owner.clear_mood_event("smell") - - if (n2o_euphoria == EUPHORIA_ACTIVE || healium_euphoria == EUPHORIA_ACTIVE) - owner.add_mood_event("chemical_euphoria", /datum/mood_event/chemical_euphoria) - else if (n2o_euphoria == EUPHORIA_INACTIVE && healium_euphoria == EUPHORIA_INACTIVE) - owner.clear_mood_event("chemical_euphoria") - // Activate mood on first flag, remove on second, do nothing on third. + // Handle chemical euphoria mood event, caused by gases such as N2O or healium. + if (n2o_euphoria == EUPHORIA_ACTIVE || healium_euphoria == EUPHORIA_ACTIVE) + owner.add_mood_event("chemical_euphoria", /datum/mood_event/chemical_euphoria) + else if (n2o_euphoria == EUPHORIA_INACTIVE && healium_euphoria == EUPHORIA_INACTIVE) + owner.clear_mood_event("chemical_euphoria") + // Activate mood on first flag, remove on second, do nothing on third. + if(has_moles) handle_breath_temperature(breath, breather) - breath.garbage_collect() - return TRUE + breath.garbage_collect() ///override this for breath handling unique to lung subtypes, breath_gas is the list of gas in the breath while gas breathed is just what is being added or removed from that list, just as they are when this is called in check_breath() /obj/item/organ/internal/lungs/proc/handle_gas_override(mob/living/carbon/human/breather, list/breath_gas, gas_breathed) return -/obj/item/organ/internal/lungs/proc/handle_too_little_breath(mob/living/carbon/human/suffocator = null, breath_pp = 0, safe_breath_min = 0, true_pp = 0) - . = 0 - if(!suffocator || !safe_breath_min) //the other args are either: Ok being 0 or Specifically handled. - return FALSE +/// Remove a volume of gas from the breath. Used to simulate absorbtion and interchange of gas in the lungs. +/// Removes all of the given gas type unless given a volume argument. +/// Returns the amount of gas theoretically removed. +/obj/item/organ/internal/lungs/proc/breathe_gas_volume(list/breath_gases, datum/gas/remove_gas, datum/gas/exchange_gas = null, volume = INFINITY) + volume = min(volume, breath_gases[remove_gas][MOLES]) + breath_gases[remove_gas][MOLES] -= volume + if(exchange_gas) + breath_gases[exchange_gas][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/internal/lungs/proc/handle_suffocation(mob/living/carbon/human/suffocator = null, breath_pp = 0, safe_breath_min = 0, true_pp = 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(breath_pp > 0) - var/ratio = safe_breath_min/breath_pp - suffocator.adjustOxyLoss(min(5*ratio, HUMAN_MAX_OXYLOSS)) // Don't fuck them up too fast (space only does HUMAN_MAX_OXYLOSS after all! - suffocator.failed_last_breath = TRUE - . = true_pp*ratio/6 - else + // 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 true_pp * ratio / 6 + // Zero pressure. + if(suffocator.health >= suffocator.crit_threshold) suffocator.adjustOxyLoss(HUMAN_MAX_OXYLOSS) - suffocator.failed_last_breath = TRUE + else + suffocator.adjustOxyLoss(HUMAN_CRIT_MAX_OXYLOSS) /obj/item/organ/internal/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/breather) // called by human/life, handles temperatures