diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm index 62ee5e457ca..337aee46c03 100644 --- a/code/datums/diseases/_MobProcs.dm +++ b/code/datums/diseases/_MobProcs.dm @@ -94,24 +94,19 @@ if(passed) disease.try_infect(src) -/mob/living/proc/AirborneContractDisease(datum/disease/disease, force_spread) - if(HAS_TRAIT(src, TRAIT_VIRUS_RESISTANCE) && prob(75)) - return - - if(((disease.spread_flags & DISEASE_SPREAD_AIRBORNE) || force_spread) && prob(min((50*disease.spreading_modifier - 1), 50))) - ForceContractDisease(disease) - -/mob/living/carbon/AirborneContractDisease(datum/disease/disease, force_spread) - if(internal) - return - if(HAS_TRAIT(src, TRAIT_NOBREATH)) - return - +/** + * Handle being contracted a disease via airborne transmission + * + * * disease - the disease datum that's infecting us + */ +/mob/living/proc/contract_airborne_disease(datum/disease/disease) + if(!can_be_spread_airborne_disease()) + return FALSE + if(!prob(min((50 * disease.spreading_modifier - 1), 50))) + return FALSE if(!disease.has_required_infectious_organ(src, ORGAN_SLOT_LUNGS)) - return - - ..() - + return FALSE + return ForceContractDisease(disease) //Proc to use when you 100% want to try to infect someone (ignoreing protective clothing and such), as long as they aren't immune /mob/living/proc/ForceContractDisease(datum/disease/D, make_copy = TRUE, del_on_fail = FALSE) @@ -136,8 +131,56 @@ return ..() -/mob/living/proc/CanSpreadAirborneDisease() - return !is_mouth_covered() +/// Checks if this mob can currently spread air based diseases. +/// Nondeterministic +/mob/living/proc/can_spread_airborne_diseases() + SHOULD_CALL_PARENT(TRUE) + if(HAS_TRAIT(src, TRAIT_NOBREATH)) + return FALSE + if(losebreath >= 1) + return FALSE + // I don't know how you are spreading via air with no head but sure + if(!get_bodypart(BODY_ZONE_HEAD)) + return TRUE + // Check both hat and mask for bio protection + // Anything above 50 individually is a shoe-in, and stacking two items at 25 is also a shoe-in + var/obj/item/clothing/hat = is_mouth_covered(ITEM_SLOT_HEAD) + var/obj/item/clothing/mask = is_mouth_covered(ITEM_SLOT_MASK) + var/total_prot = 2 * (hat?.get_armor_rating(BIO) + mask?.get_armor_rating(BIO)) + if(prob(total_prot)) + return FALSE -/mob/living/carbon/CanSpreadAirborneDisease() - return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.get_armor_rating(BIO) >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.get_armor_rating(BIO) >= 25))) + return TRUE + +/mob/living/carbon/can_spread_airborne_diseases() + if(internal || external) + return FALSE + + return ..() + +/// Checks if this mob can currently be infected by air based diseases +/// Nondeterministic +/mob/living/proc/can_be_spread_airborne_disease() + if(HAS_TRAIT(src, TRAIT_NOBREATH)) + return FALSE + if(losebreath >= 1) + return FALSE + // Spaceacillin for infection resistance + if(HAS_TRAIT(src, TRAIT_VIRUS_RESISTANCE) && prob(75)) + return FALSE + // Bio check for head AND mask + // Meaning if we're masked up and wearing a dome, we are very likely never getting sick + var/obj/item/clothing/hat = is_mouth_covered(ITEM_SLOT_HEAD) + var/obj/item/clothing/mask = is_mouth_covered(ITEM_SLOT_MASK) + var/total_prot = (hat?.get_armor_rating(BIO) + mask?.get_armor_rating(BIO)) + if(prob(total_prot)) + return FALSE + + return TRUE + +/mob/living/carbon/can_be_spread_airborne_disease() + // Using an isolated air supply is also effective + if((internal || external) && prob(75)) + return FALSE + + return ..() diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm index da0a849989e..d66bc85bb55 100644 --- a/code/datums/diseases/_disease.dm +++ b/code/datums/diseases/_disease.dm @@ -70,10 +70,33 @@ D.after_add() infectee.med_hud_set_status() + register_disease_signals() var/turf/source_turf = get_turf(infectee) log_virus("[key_name(infectee)] was infected by virus: [src.admin_details()] at [loc_name(source_turf)]") +/// Updates the spread flags set, ensuring signals are updated as necessary +/datum/disease/proc/update_spread_flags(new_flags) + if(spread_flags == new_flags) + return + + spread_flags = new_flags + unregister_disease_signals() + register_disease_signals() + +/// Register any relevant signals for the disease +/datum/disease/proc/register_disease_signals() + if(isnull(affected_mob)) + return + if(spread_flags & DISEASE_SPREAD_AIRBORNE) + RegisterSignal(affected_mob, COMSIG_CARBON_PRE_BREATHE, PROC_REF(on_breath)) + +/// Unregister any relevant signals for the disease +/datum/disease/proc/unregister_disease_signals() + if(isnull(affected_mob)) + return + UnregisterSignal(affected_mob, COMSIG_CARBON_PRE_BREATHE) + ///Proc to process the disease and decide on whether to advance, cure or make the symptoms appear. Returns a boolean on whether to continue acting on the symptoms or not. /datum/disease/proc/stage_act(seconds_per_tick, times_fired) var/slowdown = HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) ? 0.5 : 1 // spaceacillin slows stage speed by 50% @@ -219,41 +242,41 @@ if(!. || (needs_all_cures && . < cures.len)) return FALSE -//Airborne spreading -/datum/disease/proc/spread(force_spread = 0) - if(!affected_mob) - return - +/** + * Handles performing a spread-via-air + * + * Checks for stuff like "is our mouth covered" for you + * + * * spread_range - How far the disease can spread + * * force_spread - If TRUE, the disease will spread regardless of the spread_flags + * * require_facing - If TRUE, the disease will only spread if the source mob is facing the target mob + */ +/datum/disease/proc/airborne_spread(spread_range = 2, force_spread = TRUE, require_facing = FALSE) + if(isnull(affected_mob)) + return FALSE if(!(spread_flags & DISEASE_SPREAD_AIRBORNE) && !force_spread) - return - - if(affected_mob.internal) //if you keep your internals on, no airborne spread at least - return - - if(HAS_TRAIT(affected_mob, TRAIT_NOBREATH)) //also if you don't breathe - return - + return FALSE + if(affected_mob.can_spread_airborne_diseases()) + return FALSE if(!has_required_infectious_organ(affected_mob, ORGAN_SLOT_LUNGS)) //also if you lack lungs - return - - if(!affected_mob.CanSpreadAirborneDisease()) //should probably check this huh - return - - if(HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) || (affected_mob.satiety > 0 && prob(affected_mob.satiety/2))) //being full or on spaceacillin makes you less likely to spread a virus - return - - var/spread_range = 2 - - if(force_spread) - spread_range = force_spread - - var/turf/T = affected_mob.loc - if(istype(T)) - for(var/mob/living/carbon/C in oview(spread_range, affected_mob)) - var/turf/V = get_turf(C) - if(disease_air_spread_walk(T, V)) - C.AirborneContractDisease(src, force_spread) + return FALSE + if(HAS_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE) || (affected_mob.satiety > 0 && prob(affected_mob.satiety / 2))) //being full or on spaceacillin makes you less likely to spread a virus + return FALSE + var/turf/mob_loc = affected_mob.loc + if(!istype(mob_loc)) + return FALSE + for(var/mob/living/carbon/to_infect in oview(spread_range, affected_mob)) + var/turf/infect_loc = to_infect.loc + if(!istype(infect_loc)) + continue + if(require_facing && !is_source_facing_target(affected_mob, to_infect)) + continue + if(!disease_air_spread_walk(mob_loc, infect_loc)) + continue + to_infect.contract_airborne_disease(src) + return TRUE +/// Helper for checking if there is an air path between two turfs /proc/disease_air_spread_walk(turf/start, turf/end) if(!start || !end) return FALSE @@ -307,6 +330,7 @@ return "[type]" /datum/disease/proc/remove_disease() + unregister_disease_signals() LAZYREMOVE(affected_mob.diseases, src) //remove the datum from the list affected_mob.med_hud_set_status() affected_mob = null @@ -346,6 +370,13 @@ return TRUE +/// Handles spreading via air when our mob breathes +/datum/disease/proc/on_breath(datum/source, seconds_per_tick, ...) + SIGNAL_HANDLER + + if(SPT_PROB(infectivity * 4, seconds_per_tick)) + airborne_spread() + //Use this to compare severities /proc/get_disease_severity_value(severity) switch(severity) diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index bce368237ad..8c8e8e02169 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -299,22 +299,22 @@ /datum/disease/advance/proc/set_spread(spread_id) switch(spread_id) if(DISEASE_SPREAD_NON_CONTAGIOUS) - spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS + update_spread_flags(DISEASE_SPREAD_NON_CONTAGIOUS) spread_text = "None" if(DISEASE_SPREAD_SPECIAL) - spread_flags = DISEASE_SPREAD_SPECIAL + update_spread_flags(DISEASE_SPREAD_SPECIAL) spread_text = "None" if(DISEASE_SPREAD_BLOOD) - spread_flags = DISEASE_SPREAD_BLOOD + update_spread_flags(DISEASE_SPREAD_BLOOD) spread_text = "Blood" if(DISEASE_SPREAD_CONTACT_FLUIDS) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS) spread_text = "Fluids" if(DISEASE_SPREAD_CONTACT_SKIN) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN) spread_text = "Skin contact" if(DISEASE_SPREAD_AIRBORNE) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE) spread_text = "Respiration" /datum/disease/advance/proc/set_severity(level_sev) diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm index 0a29a633a30..6b46667741c 100644 --- a/code/datums/diseases/advance/symptoms/cough.dm +++ b/code/datums/diseases/advance/symptoms/cough.dm @@ -68,8 +68,7 @@ off_cooldown_coughed = TRUE else off_cooldown_coughed = FALSE - if(affected_mob.CanSpreadAirborneDisease()) - active_disease.spread(spread_range) + active_disease.airborne_spread(spread_range) if(power >= 1.5) var/obj/item/held_object = affected_mob.get_active_held_item() if(held_object && held_object.w_class == WEIGHT_CLASS_TINY) diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm index 0708841fca0..3ec095feb5c 100644 --- a/code/datums/diseases/advance/symptoms/fire.dm +++ b/code/datums/diseases/advance/symptoms/fire.dm @@ -65,7 +65,7 @@ warn_mob(living_mob) if(infective) - A.spread(advanced_stage ? 4 : 2) + A.airborne_spread(advanced_stage ? 4 : 2) /datum/symptom/fire/proc/warn_mob(mob/living/living_mob) if(prob(33.33)) diff --git a/code/datums/diseases/advance/symptoms/narcolepsy.dm b/code/datums/diseases/advance/symptoms/narcolepsy.dm index cbf6462f2c4..a137e8acd91 100644 --- a/code/datums/diseases/advance/symptoms/narcolepsy.dm +++ b/code/datums/diseases/advance/symptoms/narcolepsy.dm @@ -64,8 +64,7 @@ if(yawning) M.emote("yawn") - if(M.CanSpreadAirborneDisease()) - A.spread(6) + A.airborne_spread(6) if(5) if(prob(50)) @@ -75,5 +74,4 @@ if(yawning) M.emote("yawn") - if(M.CanSpreadAirborneDisease()) - A.spread(6) + A.airborne_spread(6) diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm index 6771d57f20b..a35e700f7b4 100644 --- a/code/datums/diseases/advance/symptoms/sneeze.dm +++ b/code/datums/diseases/advance/symptoms/sneeze.dm @@ -53,10 +53,7 @@ if(!suppress_warning) affected_mob.emote("sniff") else - if(affected_mob.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth - for(var/mob/living/exposed_mob in oview(spread_range, affected_mob)) - if(is_source_facing_target(affected_mob, exposed_mob) && disease_air_spread_walk(get_turf(affected_mob), get_turf(exposed_mob))) - exposed_mob.AirborneContractDisease(active_disease, TRUE) + active_disease.airborne_spread(spread_range, force_spread = TRUE, require_facing = TRUE) if(cartoon_sneezing) //Yeah, this can fling you around even if you have a space suit helmet on. It's, uh, bluespace snot, yeah. affected_mob.emote("sneeze") to_chat(affected_mob, span_userdanger("You are launched violently backwards by an all-mighty sneeze!")) diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm index 881709c3165..8fcc05f345b 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -450,13 +450,13 @@ /datum/disease/advance/random/event/set_spread(spread_id) switch(spread_id) if(DISEASE_SPREAD_CONTACT_FLUIDS) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS) spread_text = "Fluids" if(DISEASE_SPREAD_CONTACT_SKIN) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN) spread_text = "Skin contact" if(DISEASE_SPREAD_AIRBORNE) - spread_flags = DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE + update_spread_flags(DISEASE_SPREAD_BLOOD | DISEASE_SPREAD_CONTACT_FLUIDS | DISEASE_SPREAD_CONTACT_SKIN | DISEASE_SPREAD_AIRBORNE) spread_text = "Respiration" /** diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index e5aa87a9422..96fc7f5ecd6 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -68,10 +68,10 @@ // Second link in a breath chain, calls [carbon/proc/check_breath()] /mob/living/carbon/proc/breathe(seconds_per_tick, times_fired) var/obj/item/organ/internal/lungs = get_organ_slot(ORGAN_SLOT_LUNGS) - if(SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE) & COMSIG_CARBON_BLOCK_BREATH) + if(SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE, seconds_per_tick, times_fired) & COMSIG_CARBON_BLOCK_BREATH) return - SEND_SIGNAL(src, COMSIG_CARBON_PRE_BREATHE) + SEND_SIGNAL(src, COMSIG_CARBON_PRE_BREATHE, seconds_per_tick, times_fired) var/datum/gas_mixture/environment if(loc) @@ -477,8 +477,6 @@ for(var/datum/disease/disease as anything in diseases) if(QDELETED(disease)) //Got cured/deleted while the loop was still going. continue - if(SPT_PROB(disease.infectivity, seconds_per_tick)) - disease.spread() if(stat != DEAD || disease.process_dead) disease.stage_act(seconds_per_tick, times_fired) diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 6be62d7b2a2..25db3cdabfb 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -462,7 +462,6 @@ /obj/item/organ/internal/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