mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 20:48:56 +01:00
Makes disease airborne spread more realistic (#83423)
## About The Pull Request 1. Rather than checking every life tick to try to spread via air, it checks every breath tick. IE, every 8 seconds. 2. Wearing a mask now protects you from catching airborne diseases, rather than only protecting you from spreading it. 3. Wearing a mask is no longer guaranteed effective (unless on internals), it now scales based on BIO armor. This is doubly effectively for people wearing a mask to stop spreading to others vs wearing a mask to block spreading to them. ## Why It's Good For The Game Wearing any kind of mask means airborne viruses will never spread, but also wearing any kind of mask on the opposite end is pointless unless on internals. Kinda silly. This PR unifies those behaviors. Wearing a mask to stop spread is still rewarded, as BIO armor is doubly effective at stopping a spread attempt as blocking a spread attempt. Ultimately the goal of this is to give players a realistic way to protect themselves against airborne transmission vectors besides staying on internals 100% of the time. ## Changelog 🆑 Melbert balance: Wearing a mask can now prevent you from being spread airborne diseases. Prior, it only stopped you from spreading it yourself. balance: Wearing a mask no longer 100% guarantees you don't spread viruses to others people - it now scales on the masks BIO armor. Get a sterile mask from Medical for max safety. balance: Wearing a mask to stop you from spreading to other people is doubly effective than wearing a mask to prevent spread to yourself - Mask up for the greater good! balance: Passive airborne virus spread attempts are now done every time you breathe (every eight seconds), rather than every two seconds. The chance of spread is the same, though - it has been scaled up to accommodate. code: Cleaned up a fair bid of airborne-transmission-code. Report any oddities. /🆑
This commit is contained in:
@@ -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 ..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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"
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user