From fadbf16e1b1cef35a7f2a9243c10fb491e543f2e Mon Sep 17 00:00:00 2001
From: nikothedude <59709059+nikothedude@users.noreply.github.com>
Date: Sun, 7 Sep 2025 21:31:21 -0400
Subject: [PATCH] Asthma quirk, inhalers - Electric Boogaloo (#92747)
## About The Pull Request
Revival of https://github.com/tgstation/tgstation/pull/79691
A while back, I made this PR, but lost motivation after diving too deep
into the code soup of can_breathe and related procs. Now, I have removed
those parts, and have simplified that part of the code to the point I
think it's ready for review.
Many reviews from the previous PR have been addressed in this PR.
Details
Asthma is a 4 point negative quirk that emulates real life asthma. It
works by slowly decreasing the amount of pressure each breath you take
receives, until your lungs completely seal, ensuring death if you dont
get oxyloss meds/windpipe surgery.
Inflammation (the tracker for intensity) increases whenever you breathe
smoke, use a cigarette, metabolize histimine, or suffer an asthma
attack.
Asthma attacks have a low chance of happening every second, starting 10
minutes after you spawn and with a 20-30 grace period between attacks.
They are "diseases" that cant be outright cured by albuterol, only put
into remission. They increase inflammation at varying rates depending on
their severity, with extreme asthma attacks being a immediate threat to
your life while mild ones might not even cause inflammation. The
response to these is always the same - use your inhaler before you start
choking.
Asthmatics start with a rescue inhaler, a low-capacity inhaler loaded
with albuterol, which I will get to later.
Albuterol is a new medicine thats a little tricky to make but still
doable. It can be efficiently created with inverse convermol, or
transmutated from salbutamol and convermol. The opposite is true, with
albuterol able to be turned into salbutamol. Two canisters are available
in chemvends.
Upon use, it increases the virtual pressure of all breaths taken by 40%.
This allows for you to breathe in lower pressure environments, as well
as enhancing the effects of things like healium.
It's OD causes your diaphram to spasm, causing sporadic losebreath and
forced breathing.
Inhalers are a fancy new reagent application apparatus that uses the
INHALE reagent bitflag.
Inhalers themselves are rather unremarkable, they are merely the method
of using inhaler canisters (they also have a rotary display
approximating the uses left in a canister - just like real life
inhalers).
Inhaler canisters are the reagent containers, and are generally low
capacity. They can only be used in a inhaler, and contain aerosolized
chemicals.
Inhaler canisters and inhalers are unlocked from chemical synthesis, and
are printable for cheap from a medlathe.
In order to use a inhaler, one must uncover the mouth of a carbon and
wait a few seconds (its faster if its a self-application) before a small
amount of the reagents are delivered via the INHALE bitflag. This only
works on things currently breathing - if theyre dead, have no lungs, or
just, arent breathing - it will fail. This includes asthmatics with 100%
inflammation.
Sprites are not mine; they are from swanni and I can NOT sprite for the
life of me
## Why It's Good For The Game
1. Asthma is just cool. One of my favorite features on bay was the fact
lung damage required you to turn up the pressure on your O2 tank to
survive, and this does precisely that.
2. Its always fun to add new ways to interact with atmos as a player
that arent grossly broken, and I fail to see how a 40% increase of gas
intake will really affect balance too badly.
3. Inhalers are badass.
## Changelog
:cl:
add: Asthma quirk, based on IRL asthma
add: Inhalers, a new reagent administering method that uses INHALE
add: Albuterol, a new reagent that increases the amount of gas you
inhale by 40%
balance: Inverse convermol now forms once the reaction is done, not on
metabolize
/:cl:
---------
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
---
code/__DEFINES/alerts.dm | 2 +
.../signals/signals_mob/signals_mob_carbon.dm | 3 +
code/__DEFINES/diseases.dm | 2 +
code/__DEFINES/reagents.dm | 2 +-
code/_onclick/hud/alert.dm | 10 +
code/datums/diseases/_disease.dm | 14 +-
code/datums/diseases/asthma_attack.dm | 262 +++++++++++++++
.../datums/quirks/negative_quirks/allergic.dm | 1 +
code/datums/quirks/negative_quirks/asthma.dm | 249 ++++++++++++++
.../fluid_spread/effects_smoke.dm | 1 +
.../items/devices/scanners/health_analyzer.dm | 25 ++
code/modules/mob/living/emote.dm | 5 +
.../chemistry/reagents/medicine_reagents.dm | 81 +++++
.../chemistry/recipes/cat2_medicines.dm | 2 +-
.../reagents/chemistry/recipes/medicine.dm | 45 +++
.../reagents/reagent_containers/inhaler.dm | 314 ++++++++++++++++++
.../research/designs/medical_designs.dm | 24 ++
.../research/techweb/nodes/medbay_nodes.dm | 2 +
code/modules/surgery/asthmatic_bypass.dm | 96 ++++++
code/modules/surgery/organs/_organ.dm | 1 +
.../surgery/organs/internal/lungs/_lungs.dm | 38 ++-
code/modules/vending/medical.dm | 1 +
icons/hud/screen_alert.dmi | Bin 183019 -> 183944 bytes
icons/obj/medical/chemical.dmi | Bin 79679 -> 80643 bytes
tgstation.dme | 4 +
25 files changed, 1177 insertions(+), 7 deletions(-)
create mode 100644 code/datums/diseases/asthma_attack.dm
create mode 100644 code/datums/quirks/negative_quirks/asthma.dm
create mode 100644 code/modules/reagents/reagent_containers/inhaler.dm
create mode 100644 code/modules/surgery/asthmatic_bypass.dm
diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm
index 662711610ee..ea12830d858 100644
--- a/code/__DEFINES/alerts.dm
+++ b/code/__DEFINES/alerts.dm
@@ -14,6 +14,8 @@
#define ALERT_TOO_MUCH_NITRO "too_much_nitro"
#define ALERT_NOT_ENOUGH_NITRO "not_enough_nitro"
+#define ALERT_BRONCHODILATION "bronchodilation"
+
#define ALERT_NOT_ENOUGH_WATER "not_enough_water"
/** Mob related */
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
index fe2071df0b2..67d69dc5b05 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -179,3 +179,6 @@
/// From /mob/living/carbon/proc/set_blood_type : (mob/living/carbon/user, datum/blood_type, update_cached_blood_dna_info)
#define COMSIG_CARBON_CHANGED_BLOOD_TYPE "carbon_set_blood_type"
+
+//from base of [/obj/effect/particle_effect/fluid/smoke/proc/smoke_mob]: (seconds_per_tick)
+#define COMSIG_CARBON_EXPOSED_TO_SMOKE "carbon_exposed_to_smoke"
diff --git a/code/__DEFINES/diseases.dm b/code/__DEFINES/diseases.dm
index fe05a30e0fd..379f9a37d29 100644
--- a/code/__DEFINES/diseases.dm
+++ b/code/__DEFINES/diseases.dm
@@ -16,6 +16,8 @@ DEFINE_BITFIELD(visibility_flags, list(
#define CAN_CARRY (1<<1)
#define CAN_RESIST (1<<2)
#define CHRONIC (1<<3)
+/// Instead of instantly curing the disease, cures will simply reduce the stage
+#define INCREMENTAL_CURE (1<<4)
//Spread Flags
#define DISEASE_SPREAD_SPECIAL (1<<0)
diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm
index d2f5528bc49..58db74fa044 100644
--- a/code/__DEFINES/reagents.dm
+++ b/code/__DEFINES/reagents.dm
@@ -45,7 +45,7 @@
#define INJECT (1<<4)
/// Exclusive to just plumbing. if set we use the round robin technique else we use proportional
#define LINEAR (1<<5)
-/// Used by smoke or inhaling from a source. Smoke and cigarettes.
+/// Used by smoke or inhaling from a source. Smoke, cigarettes, and inhalers.
#define INHALE (1<<6)
///Smoke machines are both touch and inhaling
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index ec7d26d4da0..c10bc5922e2 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -199,6 +199,16 @@
//End gas alerts
+/atom/movable/screen/alert/bronchodilated
+ name = "Bronchodilated"
+ desc = "You feel like your lungs are larger than usual! You're taking deeper breaths!"
+ icon_state = "bronchodilated"
+
+/atom/movable/screen/alert/bronchoconstricted
+ name = "Bronchocontracted"
+ desc = "You feel like your lungs are smaller than usual! You might need a higher pressure environment/internals to breathe!"
+ icon_state = "bronchoconstricted"
+
/atom/movable/screen/alert/gross
name = "Grossed out."
desc = "That was kind of gross..."
diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm
index e19c19151e3..d3c8c0f7dc0 100644
--- a/code/datums/diseases/_disease.dm
+++ b/code/datums/diseases/_disease.dm
@@ -117,11 +117,13 @@
update_stage(1)
to_chat(affected_mob, span_notice("Your chronic illness is alleviated a little, though it can't be cured!"))
return
- if(SPT_PROB(cure_mod, seconds_per_tick))
- update_stage(max(stage - 1, 1))
if(disease_flags & CURABLE && SPT_PROB(cure_mod, seconds_per_tick))
- cure()
- return FALSE
+ if(disease_flags & INCREMENTAL_CURE)
+ if (!update_stage(stage - 1))
+ return FALSE
+ else
+ cure()
+ return FALSE
if(stage == max_stages && stage_peaked != TRUE) //mostly a sanity check in case we manually set a virus to max stages
stage_peaked = TRUE
@@ -233,6 +235,10 @@
stage = new_stage
if(new_stage == max_stages && !(stage_peaked)) //once a virus has hit its peak, set it to have done so
stage_peaked = TRUE
+ if (stage <= 0)
+ cure()
+ return FALSE
+ return TRUE
/datum/disease/proc/has_cure()
if(!(disease_flags & (CURABLE | CHRONIC)))
diff --git a/code/datums/diseases/asthma_attack.dm b/code/datums/diseases/asthma_attack.dm
new file mode 100644
index 00000000000..7adefd2f746
--- /dev/null
+++ b/code/datums/diseases/asthma_attack.dm
@@ -0,0 +1,262 @@
+/datum/disease/asthma_attack
+ form = "Bronchitis"
+ name = "Asthma attack"
+ desc = "Subject is undergoing a autoimmune response which threatens to close the esophagus and halt all respiration, leading to death. \
+ Minor asthma attacks may disappear on their own, but all are dangerous."
+ cure_text = "Albuterol/Surgical intervention"
+ cures = list(/datum/reagent/medicine/albuterol)
+ agent = "Inflammatory"
+ viable_mobtypes = list(/mob/living/carbon/human)
+ disease_flags = CURABLE
+ spread_flags = DISEASE_SPREAD_NON_CONTAGIOUS
+ spread_text = "Inflammatory"
+ visibility_flags = HIDDEN_PANDEMIC
+ bypasses_immunity = TRUE
+ disease_flags = CURABLE|INCREMENTAL_CURE
+ required_organ = ORGAN_SLOT_LUNGS
+ infectable_biotypes = MOB_ROBOTIC|MOB_ORGANIC|MOB_MINERAL|MOB_UNDEAD
+
+ /// The world.time after which we will begin remission.
+ var/time_to_start_remission
+
+ /// The max time, after initial infection, it will take for us to begin remission
+ var/max_time_til_remission
+ /// The min time, after initial infection, it will take for us to begin remission
+ var/min_time_til_remission
+
+ /// Are we in remission, where we stop progressing and instead slowly degrade in intensity until we remove ourselves?
+ var/in_remission = FALSE
+
+ /// The current progress to stage demotion. Resets to 0 and reduces our stage by 1 when it exceeds [progress_needed_to_demote]. Only increases when in remission.
+ var/progress_to_stage_demotion = 0
+ /// The amount of demotion progress we receive per second while in remission.
+ var/progress_to_demotion_per_second = 1
+ /// Once [progress_to_stage_demotion] exceeds or meets this, we reduce our stage.
+ var/progress_needed_to_demote = 10
+
+ /// Do we alert ghosts when we are applied?
+ var/alert_ghosts = FALSE
+
+ /// A assoc list of (severity -> string), where string will be suffixed to our name in (suffix) format.
+ var/static/list/severity_to_suffix = list(
+ DISEASE_SEVERITY_MEDIUM = "Minor",
+ DISEASE_SEVERITY_HARMFUL = "Moderate",
+ DISEASE_SEVERITY_DANGEROUS = "Severe",
+ DISEASE_SEVERITY_BIOHAZARD = "EXTREME",
+ )
+ /// A assoc list of (stringified number -> number), where the key is the stage and the number is how much inflammation we will cause the asthmatic per second.
+ var/list/stage_to_inflammation_per_second
+
+/datum/disease/asthma_attack/New()
+ . = ..()
+
+ suffix_name()
+
+ time_to_start_remission = world.time + rand(min_time_til_remission, max_time_til_remission)
+
+/datum/disease/asthma_attack/try_infect(mob/living/infectee, make_copy)
+ if (!get_asthma_quirk())
+ return FALSE
+ if (HAS_TRAIT(infectee, TRAIT_NOBREATH))
+ return FALSE
+
+ return ..()
+
+/// Adds our suffix via [severity_to_suffix] in the format of (suffix) to our name.
+/datum/disease/asthma_attack/proc/suffix_name()
+ name += " ([severity_to_suffix[severity]])"
+
+/// Returns the asthma quirk of our victim. As we can only be applied to asthmatics, this should never return null.
+/datum/disease/asthma_attack/proc/get_asthma_quirk(mob/living/target = affected_mob)
+ RETURN_TYPE(/datum/quirk/item_quirk/asthma)
+
+ return (locate(/datum/quirk/item_quirk/asthma) in target.quirks)
+
+/datum/disease/asthma_attack/stage_act(seconds_per_tick, times_fired)
+ . = ..()
+ if (!.)
+ return
+
+ if (HAS_TRAIT(affected_mob, TRAIT_NOBREATH))
+ cure()
+ return FALSE
+
+ var/datum/quirk/item_quirk/asthma/asthma_quirk = get_asthma_quirk()
+ var/inflammation = stage_to_inflammation_per_second["[stage]"]
+ if (inflammation)
+ asthma_quirk.adjust_inflammation(inflammation * seconds_per_tick)
+
+ if (!(world.time >= time_to_start_remission))
+ return
+
+ if (!in_remission)
+ in_remission = TRUE
+ stage_prob = 0
+ name += " (Remission)"
+ desc += " The attack has entered remission. It will slowly decrease in intensity before vanishing."
+ progress_to_stage_demotion += (progress_to_demotion_per_second * seconds_per_tick)
+ if (progress_to_stage_demotion >= progress_needed_to_demote)
+ progress_to_stage_demotion = 0
+ update_stage(stage - 1)
+
+// TYPES OF ASTHMA ATTACK
+
+/datum/disease/asthma_attack/minor
+ severity = DISEASE_SEVERITY_MEDIUM
+ stage_prob = 4
+
+ max_time_til_remission = 120 SECONDS
+ min_time_til_remission = 80 SECONDS
+ max_stages = 3
+
+ cure_chance = 20
+
+ stage_to_inflammation_per_second = list(
+ "2" = 0.3,
+ "3" = 0.6,
+ )
+
+/datum/disease/asthma_attack/minor/stage_act(seconds_per_tick, times_fired)
+ . = ..()
+ if (!.)
+ return FALSE
+
+ if (SPT_PROB(5, seconds_per_tick))
+ to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus.")))
+
+/datum/disease/asthma_attack/moderate
+ severity = DISEASE_SEVERITY_HARMFUL
+ stage_prob = 5
+
+ max_time_til_remission = 120 SECONDS
+ min_time_til_remission = 80 SECONDS
+ max_stages = 4
+
+ cure_chance = 20
+
+ stage_to_inflammation_per_second = list(
+ "2" = 1,
+ "3" = 2,
+ "4" = 4,
+ )
+
+/datum/disease/asthma_attack/moderate/stage_act(seconds_per_tick, times_fired)
+ . = ..()
+ if (!.)
+ return FALSE
+
+ if (SPT_PROB(15, seconds_per_tick))
+ to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus.")))
+
+ if (stage < 4 || !SPT_PROB(10, seconds_per_tick))
+ return
+ to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!"))
+ affected_mob.losebreath++
+
+
+/datum/disease/asthma_attack/severe
+ severity = DISEASE_SEVERITY_DANGEROUS
+ stage_prob = 6
+
+ max_time_til_remission = 80 SECONDS
+ min_time_til_remission = 60 SECONDS
+ max_stages = 5
+
+ cure_chance = 20
+
+ stage_to_inflammation_per_second = list(
+ "2" = 1,
+ "3" = 3,
+ "4" = 6,
+ "5" = 8,
+ )
+
+ visibility_flags = HIDDEN_SCANNER
+ alert_ghosts = TRUE
+
+/datum/disease/asthma_attack/severe/stage_act(seconds_per_tick, times_fired)
+ . = ..()
+ if (!.)
+ return FALSE
+
+ if (stage > 1)
+ visibility_flags &= ~HIDDEN_SCANNER // revealed
+
+ if (SPT_PROB(15, seconds_per_tick))
+ to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus.")))
+ else if (SPT_PROB(20, seconds_per_tick))
+ affected_mob.emote("cough")
+
+ if (stage < 4 || !SPT_PROB(15, seconds_per_tick))
+ return
+ to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!"))
+ affected_mob.losebreath++
+
+/datum/disease/asthma_attack/critical
+ severity = DISEASE_SEVERITY_BIOHAZARD
+ stage_prob = 85
+
+ max_time_til_remission = 60 SECONDS // this kills you extremely quickly, so its fair
+ min_time_til_remission = 40 SECONDS
+ max_stages = 6
+
+ cure_chance = 30
+
+ stage_to_inflammation_per_second = list(
+ "1" = 5,
+ "2" = 6,
+ "3" = 7,
+ "4" = 10,
+ "5" = 20,
+ "6" = 500, // youre fucked frankly
+ )
+
+ /// Have we warned our user of the fact they are at stage 5? If no, and are at or above stage five, we send a warning and set this to true.
+ var/warned_user = FALSE
+ /// Have we ever reached our max stage? If no, and we are at our max stage, we send a ominous message warning them of their imminent demise.
+ var/max_stage_reached = FALSE
+
+/datum/disease/asthma_attack/critical/stage_act(seconds_per_tick, times_fired)
+ . = ..()
+ if (!.)
+ return FALSE
+
+ if (stage < 5)
+ if (SPT_PROB(75, seconds_per_tick))
+ to_chat(affected_mob, span_warning(pick("Mucous runs down the back of your throat.", "You swallow excess mucus.")))
+
+ var/wheeze_chance
+ if (!warned_user && stage >= 5)
+ to_chat(affected_mob, span_userdanger("You feel like your lungs are filling with fluid! It's getting incredibly hard to breathe!"))
+ warned_user = TRUE
+
+ switch (stage)
+ if (1)
+ wheeze_chance = 0
+ if (2)
+ wheeze_chance = 20
+ if (3)
+ wheeze_chance = 40
+ if (4)
+ wheeze_chance = 60
+ if (5)
+ wheeze_chance = 80
+ if (!in_remission)
+ stage_prob = 10 // slow it down significantly
+ if (6)
+ if (!max_stage_reached)
+ max_stage_reached = TRUE
+ to_chat(affected_mob, span_userdanger("You feel your windpipe squeeze shut!"))
+ wheeze_chance = 0
+ if (SPT_PROB(10, seconds_per_tick))
+ affected_mob.emote("gag")
+ var/datum/quirk/item_quirk/asthma/asthma_quirk = get_asthma_quirk()
+ asthma_quirk.adjust_inflammation(INFINITY)
+
+ if (SPT_PROB(wheeze_chance, seconds_per_tick))
+ affected_mob.emote("wheeze")
+
+ if (stage < 4 || !SPT_PROB(15, seconds_per_tick))
+ return
+ to_chat(affected_mob, span_warning("You briefly choke on the mucus piling in your throat!"))
+ affected_mob.losebreath++
diff --git a/code/datums/quirks/negative_quirks/allergic.dm b/code/datums/quirks/negative_quirks/allergic.dm
index a2442adab10..25621f7970e 100644
--- a/code/datums/quirks/negative_quirks/allergic.dm
+++ b/code/datums/quirks/negative_quirks/allergic.dm
@@ -22,6 +22,7 @@
/datum/reagent/medicine/diphenhydramine,
/datum/reagent/medicine/sansufentanyl,
/datum/reagent/medicine/salglu_solution,
+ /datum/reagent/medicine/albuterol,
)
var/allergy_string
diff --git a/code/datums/quirks/negative_quirks/asthma.dm b/code/datums/quirks/negative_quirks/asthma.dm
new file mode 100644
index 00000000000..6683869a646
--- /dev/null
+++ b/code/datums/quirks/negative_quirks/asthma.dm
@@ -0,0 +1,249 @@
+/datum/quirk/item_quirk/asthma
+ name = "Asthma"
+ desc = "You suffer from asthma, a inflammatory disorder that causes your airpipe to squeeze shut! Be careful around smoke!"
+ icon = FA_ICON_LUNGS_VIRUS
+ value = -4 // trivialized by NOBREATH but still quite dangerous
+ gain_text = span_danger("You have a harder time breathing.")
+ lose_text = span_notice("You suddenly feel like your lungs just got a lot better at breathing!")
+ medical_record_text = "Patient suffers from asthma."
+ hardcore_value = 2
+ quirk_flags = QUIRK_HUMAN_ONLY
+ mail_goodies = list(/obj/item/reagent_containers/inhaler_canister/albuterol)
+
+ /// At this percentage of inflammation, our lung pressure mult reaches 0. From 0-1.
+ var/hit_max_mult_at_inflammation_percent = 0.9
+
+ /// Current inflammation of the lungs.
+ var/inflammation = 0
+ /// Highest possible inflammation. Interacts with [hit_max_mult_at_inflammation_percent]
+ var/max_inflammation = 500
+
+ /// The amount [inflammation] reduces every second while our owner is off stasis and alive.
+ var/passive_inflammation_reduction = 0.15
+
+ /// The amount of inflammation we will receive when our owner breathes smoke.
+ var/inflammation_on_smoke = 7.5
+
+ /// If our owner is metabolizing histamine, inflammation will increase by this per tick.
+ var/histamine_inflammation = 2
+ /// If our owner is ODing on histamine, inflammation will increase by this per tick.
+ var/histamine_OD_inflammation = 10 // allergic reactions tend to fuck people up
+
+ /// A tracker variable for how much albuterol has been inhaled.
+ var/inhaled_albuterol = 0
+ /// If [inhaled_albuterol] is above 0, we will reduce inflammation by this much per tick.
+ var/albuterol_inflammation_reduction = 3
+ /// When albuterol is inhaled, inflammation will be reduced via (inhaled_albuterol * albuterol_inflammation_reduction * albuterol_immediate_reduction_mult)
+ var/albuterol_immediate_reduction_mult = 4
+
+ /// The current asthma attack trying to kill our owner.
+ var/datum/disease/asthma_attack/current_attack
+ /// Can we cause an asthma attack?
+ COOLDOWN_DECLARE(next_attack_cooldown)
+
+ /// world.time + this is the time the first attack can happen. Used on spawn.
+ var/time_first_attack_can_happen = 10 MINUTES
+
+ /// After an attack ends, this is the minimum time we must wait before we attack again.
+ var/min_time_between_attacks = 15 MINUTES
+ /// After an attack ends, this is the maximum time we must wait before we attack again.
+ var/max_time_between_attacks = 25 MINUTES
+
+ /// Every second, an asthma attack can happen via this probability. 0-1.
+ var/chance_for_attack_to_happen_per_second = 0.05
+
+ /// Assoc list of (/datum/disease/asthma_attack typepath -> number). Used in pickweight for when we pick a random asthma attack to apply.
+ var/static/list/asthma_attack_rarities = list(
+ /datum/disease/asthma_attack/minor = 300,
+ /datum/disease/asthma_attack/moderate = 400,
+ /datum/disease/asthma_attack/severe = 100,
+ /datum/disease/asthma_attack/critical = 1, // this can quickly kill you, so its rarity is justified
+ )
+
+/datum/quirk/item_quirk/asthma/add_unique(client/client_source)
+ . = ..()
+
+ var/obj/item/inhaler/albuterol/asthma/rescue_inhaler = new(get_turf(quirk_holder))
+ give_item_to_holder(rescue_inhaler, list(LOCATION_BACKPACK, LOCATION_HANDS), flavour_text = "You can use this to quickly relieve the symptoms of your asthma.")
+
+ RegisterSignal(quirk_holder, COMSIG_CARBON_EXPOSED_TO_SMOKE, PROC_REF(holder_exposed_to_smoke))
+ RegisterSignal(quirk_holder, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(organ_removed))
+ RegisterSignal(quirk_holder, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(exposed_to_reagents))
+ RegisterSignal(quirk_holder, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_full_heal))
+ RegisterSignal(quirk_holder, COMSIG_LIVING_LIFE, PROC_REF(on_life))
+
+ COOLDOWN_START(src, next_attack_cooldown, time_first_attack_can_happen)
+
+/datum/quirk/item_quirk/asthma/remove()
+ . = ..()
+
+ current_attack?.cure()
+ UnregisterSignal(quirk_holder, COMSIG_CARBON_EXPOSED_TO_SMOKE, COMSIG_CARBON_LOSE_ORGAN, COMSIG_ATOM_EXPOSE_REAGENTS, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_LIVING_LIFE)
+
+/datum/quirk/item_quirk/asthma/proc/on_life(mob/living/source, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+
+ if (quirk_holder.stat == DEAD)
+ return
+
+ if (HAS_TRAIT(quirk_holder, TRAIT_STASIS) || HAS_TRAIT(quirk_holder, TRAIT_NO_TRANSFORM))
+ return
+
+ var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
+ if (isnull(holder_lungs))
+ return
+
+ adjust_inflammation(-passive_inflammation_reduction * seconds_per_tick)
+
+ var/datum/reagent/toxin/histamine/holder_histamine = quirk_holder.reagents.has_reagent(/datum/reagent/toxin/histamine)
+ if (holder_histamine)
+ if (holder_histamine.overdosed) // uh oh!
+ if (SPT_PROB(15, seconds_per_tick))
+ to_chat(quirk_holder, span_boldwarning("You feel your neck swelling, squeezing on your windpipe more and more!"))
+ adjust_inflammation(histamine_OD_inflammation * seconds_per_tick)
+ else
+ if (SPT_PROB(5, seconds_per_tick))
+ to_chat(quirk_holder, span_warning("You find yourself wheezing a little harder as your neck swells..."))
+ adjust_inflammation(histamine_inflammation * seconds_per_tick)
+
+ var/datum/reagent/medicine/albuterol/albuterol = quirk_holder.reagents.has_reagent(/datum/reagent/medicine/albuterol)
+ if (!albuterol) // sanity - couldve been purged. can be 0 or null which is why we just use a !
+ inhaled_albuterol = 0
+ else
+ inhaled_albuterol = min(albuterol.volume, inhaled_albuterol)
+
+ if (inhaled_albuterol > 0)
+ adjust_inflammation(-(albuterol_inflammation_reduction * seconds_per_tick))
+
+ // asthma attacks dont happen if theres no client, because they can just kill you and some need immediate response
+ else if (quirk_holder.client && isnull(current_attack) && COOLDOWN_FINISHED(src, next_attack_cooldown) && SPT_PROB(chance_for_attack_to_happen_per_second, seconds_per_tick))
+ do_asthma_attack()
+
+/// Causes an asthma attack via infecting our owner with the attack disease. Notifies ghosts.
+/datum/quirk/item_quirk/asthma/proc/do_asthma_attack()
+ var/datum/disease/asthma_attack/typepath = pick_weight(asthma_attack_rarities)
+
+ current_attack = new typepath
+ current_attack.infect(quirk_holder, make_copy = FALSE) // dont leave make_copy on TRUE. worst mistake ive ever made
+ RegisterSignal(current_attack, COMSIG_QDELETING, PROC_REF(attack_deleting))
+
+ if (current_attack.alert_ghosts)
+ notify_ghosts("[quirk_holder] is having an asthma attack: [current_attack.name]!", source = quirk_holder, notify_flags = NOTIFY_CATEGORY_NOFLASH, header = "Asthma attack!")
+
+/// Setter proc for [inflammation]. Adjusts the amount by lung health, adjusts pressure mult, gives feedback messages if silent is FALSE.
+/datum/quirk/item_quirk/asthma/proc/adjust_inflammation(amount, silent = FALSE)
+ var/old_inflammation = inflammation
+
+ var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
+ var/health_mult = get_lung_health_mult(holder_lungs)
+ if (amount > 0) // make it worse
+ amount *= (2 - health_mult)
+ else // reduce the reduction
+ amount *= health_mult
+
+ var/old_pressure_mult = get_pressure_mult()
+ inflammation = (clamp(inflammation + amount, 0, max_inflammation))
+ var/difference = (old_inflammation - inflammation)
+ if (difference != 0)
+ var/new_pressure_mult = get_pressure_mult()
+ var/pressure_difference = new_pressure_mult - old_pressure_mult
+
+ holder_lungs?.adjust_received_pressure_mult(pressure_difference)
+
+ if (!silent)
+ INVOKE_ASYNC(src, PROC_REF(do_inflammation_change_feedback), difference)
+
+/// Setter proc for [inhaled_albuterol]. Adjusts inflammation immediately.
+/datum/quirk/item_quirk/asthma/proc/adjust_albuterol_levels(adjustment)
+ if (adjustment > 0)
+ var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
+
+ if (isnull(holder_lungs) || holder_lungs.received_pressure_mult <= 0) // it didnt go into the lungs get fucked
+ return
+
+ adjust_inflammation(-(albuterol_inflammation_reduction * albuterol_immediate_reduction_mult))
+
+ inhaled_albuterol += adjustment
+
+/// Returns the pressure mult to be applied to our lungs.
+/datum/quirk/item_quirk/asthma/proc/get_pressure_mult()
+ var/virtual_max = (max_inflammation * hit_max_mult_at_inflammation_percent)
+
+ return (1 - (min(inflammation/virtual_max, 1)))
+
+/// Sends feedback to our owner of which direction our asthma is intensifying/recovering.
+/datum/quirk/item_quirk/asthma/proc/do_inflammation_change_feedback(difference)
+ var/change_mult = 1 + (difference / 300) // 300 is arbitrary
+ if (difference > 0) // it decreased
+ if (prob(1 * change_mult))
+ // in my experience with asthma an inhaler causes a bunch of mucous and you tend to cough it up
+ to_chat(quirk_holder, span_notice("The phlem in your throat forces you to cough!"))
+ quirk_holder.emote("cough")
+
+ else if (difference < 0)// it increased
+ if (prob(1 * change_mult))
+ quirk_holder.emote("wheeze")
+ if (prob(5 * change_mult))
+ to_chat(quirk_holder, span_warning("You feel your windpipe tightening..."))
+
+/// Returns the % of health our lungs have, from 1-0. Used in reducing recovery and intensifying inflammation.
+/datum/quirk/item_quirk/asthma/proc/get_lung_health_mult()
+ var/mob/living/carbon/carbon_quirk_holder = quirk_holder
+ var/obj/item/organ/lungs/holder_lungs = carbon_quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
+ if (isnull(holder_lungs))
+ return 1
+ if (holder_lungs.organ_flags & ORGAN_FAILING)
+ return 0
+ return (1 - (holder_lungs.damage / holder_lungs.maxHealth))
+
+/// Signal proc for when we are exposed to smoke. Increases inflammation.
+/datum/quirk/item_quirk/asthma/proc/holder_exposed_to_smoke(datum/signal_source, seconds_per_tick)
+ SIGNAL_HANDLER
+
+ adjust_inflammation(inflammation_on_smoke * seconds_per_tick)
+
+/// Signal proc for when our lungs are removed. Resets all our variables.
+/datum/quirk/item_quirk/asthma/proc/organ_removed(datum/signal_source, obj/item/organ/removed)
+ SIGNAL_HANDLER
+
+ if (istype(removed, /obj/item/organ/lungs))
+ reset_asthma()
+
+/// Signal proc for when our owner receives reagents. If we receive albuterol via inhalation, we adjust inhaled albuterol by that amount. If we are smoking, we increase inflammation.
+/datum/quirk/item_quirk/asthma/proc/exposed_to_reagents(atom/source, list/reagents, datum/reagents/source_reagents, methods, show_message)
+ SIGNAL_HANDLER
+
+ var/final_total = 0
+
+ for (var/datum/reagent/reagent as anything in reagents)
+ var/amount = reagents[reagent]
+ if (istype(reagent, /datum/reagent/medicine/albuterol))
+ adjust_albuterol_levels(amount)
+ final_total += amount
+
+ if (!(methods & INHALE))
+ return
+ if (istype(source_reagents.my_atom, /obj/item/cigarette)) // smoking is bad, kids
+ adjust_inflammation(inflammation_on_smoke * final_total * 5)
+
+/// Signal proc for when our asthma attack qdels. Unsets our refs to it and resets [next_attack_cooldown].
+/datum/quirk/item_quirk/asthma/proc/attack_deleting(datum/signal_source)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(current_attack, COMSIG_QDELETING)
+ current_attack = null
+
+ COOLDOWN_START(src, next_attack_cooldown, rand(min_time_between_attacks, max_time_between_attacks))
+
+/// Signal handler for COMSIG_LIVING_POST_FULLY_HEAL. Heals our asthma.
+/datum/quirk/item_quirk/asthma/proc/on_full_heal(datum/signal_source, heal_flags)
+ SIGNAL_HANDLER
+
+ if (heal_flags & HEAL_ORGANS)
+ reset_asthma()
+
+/// Resets our asthma to normal. No inflammation, no pressure mult.
+/datum/quirk/item_quirk/asthma/proc/reset_asthma()
+ inflammation = 0
+ var/obj/item/organ/lungs/holder_lungs = quirk_holder.get_organ_slot(ORGAN_SLOT_LUNGS)
+ holder_lungs?.set_received_pressure_mult(holder_lungs::received_pressure_mult)
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
index c9e68ce6ede..6d71edad180 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
@@ -124,6 +124,7 @@
smoker.smoke_delay = TRUE
addtimer(VARSET_CALLBACK(smoker, smoke_delay, FALSE), 1 SECONDS)
+ SEND_SIGNAL(smoker, COMSIG_CARBON_EXPOSED_TO_SMOKE, seconds_per_tick)
return TRUE
/**
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 0da89a89252..d8033149045 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -432,6 +432,31 @@
Possible Cure: [disease.cure_text]\
"
+
+ // Lungs
+ var/obj/item/organ/lungs/lungs = target.get_organ_slot(ORGAN_SLOT_LUNGS)
+ if (lungs)
+ var/initial_pressure_mult = lungs::received_pressure_mult
+ if (lungs.received_pressure_mult != initial_pressure_mult)
+ var/tooltip
+ var/dilation_text
+ var/beginning_text = "Lung Dilation: "
+ if (lungs.received_pressure_mult > initial_pressure_mult) // higher than usual
+ beginning_text = span_blue("[beginning_text]")
+ dilation_text = span_blue("[(lungs.received_pressure_mult * 100) - 100]%")
+ tooltip = "Subject's lungs are dilated and breathing more air than usual. Increases the effectiveness of healium and other gases."
+ else
+ beginning_text = span_danger("[beginning_text]")
+ if (lungs.received_pressure_mult <= 0) // lethal
+ dilation_text = span_bolddanger("[lungs.received_pressure_mult * 100]%")
+ tooltip = "Subject's lungs are completely shut. Subject is unable to breathe and requires emergency surgery. If asthmatic, perform asthmatic bypass surgery and adminster albuterol inhalant. Otherwise, replace lungs."
+ else
+ dilation_text = span_danger("[lungs.received_pressure_mult * 100]%")
+ tooltip = "Subject's lungs are partially shut. If unable to breathe, administer a high-pressure internals tank or replace lungs. If asthmatic, inhaled albuterol or bypass surgery will likely help."
+
+ var/lung_message = beginning_text + conditional_tooltip(dilation_text, tooltip, TRUE)
+ render_list += lung_message
+
// Time of death
if(target.station_timestamp_timeofdeath && !target.appears_alive())
render_list += "
"
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 1e9404f334d..8f1fd0b431d 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -398,6 +398,11 @@
return
return user.dna.species.get_cough_sound(user)
+/datum/emote/living/wheeze
+ key = "wheeze"
+ key_third_person = "wheezes"
+ message = "wheezes!"
+ emote_type = EMOTE_AUDIBLE
/datum/emote/living/pout
key = "pout"
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 7f69044234d..53cc9d8bf2b 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -566,6 +566,87 @@
if(need_mob_update)
return UPDATE_MOB_HEALTH
+/datum/reagent/medicine/albuterol
+ name = "Albuterol"
+ description = "A potent bronchodilator capable of increasing the amount of gas inhaled by the lungs. Is highly effective at shutting down asthma attacks, \
+ but only when inhaled. Overdose causes over-dilation, resulting in reduced lung function. "
+ taste_description = "bitter and salty air"
+ overdose_threshold = 30
+ color = "#8df5f0"
+ metabolization_rate = REAGENTS_METABOLISM
+ ph = 4
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ default_container = /obj/item/reagent_containers/inhaler_canister
+
+ /// The decrement we will apply to the received_pressure_mult of our targets lungs.
+ var/pressure_mult_increment = 0.4
+ /// After this many cycles of overdose, we activate secondary effects.
+ var/secondary_overdose_effect_cycle_threshold = 40
+ /// We stop increasing stamina damage once we reach this number.
+ var/maximum_od_stamina_damage = 80
+
+/datum/reagent/medicine/albuterol/on_mob_metabolize(mob/living/affected_mob)
+ . = ..()
+
+ if (!iscarbon(affected_mob))
+ return
+
+ // has additional effects on asthma, but that's handled in the quirk
+
+ RegisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(holder_lost_organ))
+ RegisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(holder_gained_organ))
+ var/mob/living/carbon/carbon_mob = affected_mob
+ var/obj/item/organ/lungs/holder_lungs = carbon_mob.get_organ_slot(ORGAN_SLOT_LUNGS)
+ holder_lungs?.adjust_received_pressure_mult(pressure_mult_increment)
+
+/datum/reagent/medicine/albuterol/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+
+ if (!iscarbon(affected_mob))
+ return
+
+ UnregisterSignal(affected_mob, list(COMSIG_CARBON_LOSE_ORGAN, COMSIG_CARBON_GAIN_ORGAN))
+ var/mob/living/carbon/carbon_mob = affected_mob
+ var/obj/item/organ/lungs/holder_lungs = carbon_mob.get_organ_slot(ORGAN_SLOT_LUNGS)
+ holder_lungs?.adjust_received_pressure_mult(-pressure_mult_increment)
+
+/datum/reagent/medicine/albuterol/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+
+ if (!iscarbon(affected_mob))
+ return
+
+ var/mob/living/carbon/carbon_mob = affected_mob
+ if (SPT_PROB(25, seconds_per_tick))
+ carbon_mob.adjust_jitter_up_to(2 SECONDS, 20 SECONDS)
+ if (SPT_PROB(35, seconds_per_tick))
+ if (prob(60))
+ carbon_mob.losebreath += 1
+ to_chat(affected_mob, span_danger("Your diaphram spasms and you find yourself unable to breathe!"))
+ else
+ carbon_mob.breathe(seconds_per_tick, times_fired)
+ to_chat(affected_mob, span_danger("Your diaphram spasms and you unintentionally take a breath!"))
+
+ if (current_cycle > secondary_overdose_effect_cycle_threshold)
+ if (SPT_PROB(30, seconds_per_tick))
+ carbon_mob.adjust_eye_blur_up_to(6 SECONDS, 30 SECONDS)
+ if (carbon_mob.getStaminaLoss() < maximum_od_stamina_damage)
+ carbon_mob.adjustStaminaLoss(seconds_per_tick)
+
+/datum/reagent/medicine/albuterol/proc/holder_lost_organ(datum/source, obj/item/organ/lost)
+ SIGNAL_HANDLER
+
+ if (istype(lost, /obj/item/organ/lungs))
+ var/obj/item/organ/lungs/holder_lungs = lost
+ holder_lungs.adjust_received_pressure_mult(-pressure_mult_increment)
+
+/datum/reagent/medicine/albuterol/proc/holder_gained_organ(datum/source, obj/item/organ/gained)
+ SIGNAL_HANDLER
+
+ if (istype(gained, /obj/item/organ/lungs))
+ var/obj/item/organ/lungs/holder_lungs = gained
+ holder_lungs.adjust_received_pressure_mult(pressure_mult_increment)
+
/datum/reagent/medicine/ephedrine
name = "Ephedrine"
description = "Increases resistance to batons and movement speed, giving you hand cramps. Overdose deals toxin damage and inhibits breathing."
diff --git a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm
index 802d5a820e2..8600f1ecdb7 100644
--- a/code/modules/reagents/chemistry/recipes/cat2_medicines.dm
+++ b/code/modules/reagents/chemistry/recipes/cat2_medicines.dm
@@ -179,7 +179,7 @@
H_ion_release = -1
rate_up_lim = 50
purity_min = 0.25
- reaction_flags = REACTION_PH_VOL_CONSTANT
+ reaction_flags = REACTION_PH_VOL_CONSTANT|REACTION_CLEAR_INVERSE
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OXY
/datum/chemical_reaction/medicine/convermol/reaction_step(datum/reagents/holder, datum/equilibrium/reaction, delta_t, delta_ph, step_reaction_vol)
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 07d81164906..40639b2ec30 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -157,6 +157,51 @@
required_reagents = list(/datum/reagent/medicine/sal_acid = 1, /datum/reagent/lithium = 1, /datum/reagent/aluminium = 1, /datum/reagent/bromine = 1, /datum/reagent/ammonia = 1)
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OXY
+/datum/chemical_reaction/medicine/albuterol_creation
+ results = list(/datum/reagent/medicine/albuterol = 15)
+ required_reagents = list(/datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3, /datum/reagent/inverse/healing/convermol = 1)
+ reaction_tags = REACTION_TAG_MODERATE | REACTION_TAG_ORGAN | REACTION_TAG_OTHER
+ required_temp = 400
+ optimal_temp = 600
+ overheat_temp = 900
+
+/datum/chemical_reaction/medicine/salbutamol_to_albuterol
+ results = list(/datum/reagent/medicine/albuterol = 4, /datum/reagent/medicine/sal_acid = 0.5, /datum/reagent/ammonia = 0.5)
+ required_catalysts = list(/datum/reagent/toxin/acid = 1)
+ required_reagents = list(/datum/reagent/medicine/salbutamol = 5, /datum/reagent/medicine/c2/convermol = 1)
+ reaction_tags = REACTION_TAG_MODERATE | REACTION_TAG_ORGAN | REACTION_TAG_OTHER
+ required_temp = 500
+ optimal_temp = 610
+ overheat_temp = 980
+ thermic_constant = 75
+ rate_up_lim = 10
+ mix_message = "The solution rapidly changes colors, boiling into a pale blue."
+
+/datum/chemical_reaction/medicine/albuterol_to_salbutamol
+ results = list(/datum/reagent/medicine/salbutamol = 2, /datum/reagent/ammonia = 1)
+ required_catalysts = list(/datum/reagent/toxin/acid = 1)
+ required_reagents = list(/datum/reagent/medicine/albuterol = 3, /datum/reagent/oxygen = 1)
+ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_ORGAN | REACTION_TAG_OTHER
+ required_temp = 300
+ optimal_temp = 500
+ overheat_temp = 800
+ mix_message = "The solution breaks apart, turning a deeper blue."
+
+/datum/chemical_reaction/medicine/albuterol_to_inverse_convermol
+ results = list(/datum/reagent/inverse/healing/convermol = 1, /datum/reagent/lithium = 3, /datum/reagent/aluminium = 3, /datum/reagent/bromine = 3)
+ required_catalysts = list(/datum/reagent/toxin/acid/fluacid = 1)
+ required_reagents = list(/datum/reagent/medicine/albuterol = 5)
+ reaction_tags = REACTION_TAG_MODERATE | REACTION_TAG_ORGAN | REACTION_TAG_OTHER
+ required_temp = 900
+ optimal_temp = 920
+ overheat_temp = 990
+ thermic_constant = 25
+ mix_message = "The solution rapidly breaks apart, turning a mix of colors."
+
+/datum/chemical_reaction/medicine/albuterol_to_inverse_convermol/overheated(datum/reagents/holder, datum/equilibrium/equilibrium, impure = FALSE)
+ var/bonus = impure ? 2 : 1
+ explode_smoke(holder, equilibrium, 7.5 * bonus, TRUE, TRUE)
+
/datum/chemical_reaction/medicine/ephedrine
results = list(/datum/reagent/medicine/ephedrine = 4)
required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/fuel/oil = 1, /datum/reagent/hydrogen = 1, /datum/reagent/diethylamine = 1)
diff --git a/code/modules/reagents/reagent_containers/inhaler.dm b/code/modules/reagents/reagent_containers/inhaler.dm
new file mode 100644
index 00000000000..5600625df6c
--- /dev/null
+++ b/code/modules/reagents/reagent_containers/inhaler.dm
@@ -0,0 +1,314 @@
+/obj/item/inhaler
+ name = "inhaler"
+ desc = "A small device capable of administering short bursts of aerosolized chemicals. Requires a canister to function."
+ w_class = WEIGHT_CLASS_SMALL
+
+ icon = 'icons/obj/medical/chemical.dmi'
+ icon_state = "inhaler_generic"
+
+ custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.1)
+
+ /// The currently installed canister, from which we get our reagents. Nullable.
+ var/obj/item/reagent_containers/inhaler_canister/canister
+ /// The path for our initial canister to be generated by. If not null, we start with that canister type.
+ var/obj/item/reagent_containers/inhaler_canister/initial_casister_path
+
+ /// The underlay of our canister, if one is installed.
+ var/mutable_appearance/canister_underlay
+ /// The y offset to be applied to [canister_underlay].
+ var/canister_underlay_y_offset = -2
+ /// If true, we will show a rotary display with how many puffs we can be used for until the canister runs out.
+ var/show_puffs_left = TRUE // this is how real inhalers work
+
+/obj/item/inhaler/Initialize(mapload)
+ . = ..()
+ if (ispath(initial_casister_path, /obj/item/reagent_containers/inhaler_canister))
+ set_canister(new initial_casister_path)
+
+/obj/item/inhaler/Destroy(force)
+ QDEL_NULL(canister)
+
+ return ..()
+
+/obj/item/inhaler/handle_deconstruct(disassembled)
+ . = ..()
+
+ canister?.forceMove(drop_location())
+
+/obj/item/inhaler/proc/update_canister_underlay()
+ if (isnull(canister))
+ underlays -= canister_underlay
+ canister_underlay = null
+ else if (isnull(canister_underlay))
+ canister_underlay = mutable_appearance(canister.icon, canister.icon_state)
+ canister_underlay.pixel_z = canister_underlay_y_offset
+ underlays += canister_underlay
+
+/obj/item/inhaler/examine(mob/user)
+ . = ..()
+
+ if (isnull(canister))
+ return
+
+ . += span_blue("It seems to have [canister] inserted.")
+ if (!show_puffs_left)
+ return
+
+ var/puffs_left = canister.get_puffs_left()
+ if (puffs_left > 0)
+ puffs_left = span_blue("[puffs_left]")
+ else
+ puffs_left = span_danger("[puffs_left]")
+ . += "Its rotary display shows its canister can be used [puffs_left] more times."
+
+/obj/item/inhaler/Exited(atom/movable/gone, direction)
+ . = ..()
+
+ if (gone == canister)
+ set_canister(null, move_canister = FALSE)
+
+
+/obj/item/inhaler/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ if (!isliving(interacting_with))
+ return ..() // default behavior
+ var/mob/living/target_mob = interacting_with
+
+ if (!can_puff(target_mob, user))
+ return NONE
+
+ var/puff_timer = 0
+
+ var/pre_use_visible_message
+ var/pre_use_self_message
+ var/pre_use_target_message
+
+ var/post_use_visible_message
+ var/post_use_self_message
+ var/post_use_target_message
+
+ if (target_mob == user) // no need for a target message
+ puff_timer = canister.self_administer_delay
+
+ pre_use_visible_message = span_notice("[user] puts [src] to [user.p_their()] lips, fingers on the canister...")
+ pre_use_self_message = span_notice("You put [src] to your lips and put pressure on the canister...")
+
+ post_use_visible_message = span_notice("[user] takes a puff of [src]!")
+ post_use_self_message = span_notice("You take a puff of [src]!")
+ else
+ puff_timer = canister.other_administer_delay
+
+ pre_use_visible_message = span_warning("[user] tries to force [src] between [target_mob]'s lips...")
+ pre_use_self_message = span_notice("You try to put [src] to [target_mob]'s lips...")
+ pre_use_target_message = span_userdanger("[user] tries to force [src] between your lips!")
+
+ post_use_visible_message = span_warning("[user] forces [src] between [target_mob]'s lips and pushes the canister down!")
+ post_use_self_message = span_notice("You force [src] between [target_mob]'s lips and press on the canister!")
+ post_use_target_message = span_userdanger("[user] forces [src] between your lips and presses on the canister, filling your lungs with aerosol!")
+
+ if (puff_timer > 0)
+ user.visible_message(pre_use_visible_message, ignored_mobs = list(user, target_mob))
+ to_chat(user, pre_use_self_message)
+ if (pre_use_target_message)
+ to_chat(target_mob, pre_use_target_message)
+ if (!do_after(user, puff_timer, src))
+ return NONE
+ if (!can_puff(target_mob, user)) // sanity
+ return NONE
+
+ user.visible_message(post_use_visible_message, ignored_mobs = list(user, target_mob))
+ to_chat(user, post_use_self_message)
+ if (post_use_target_message)
+ to_chat(target_mob, post_use_target_message)
+
+ canister.puff(user, target_mob)
+
+/obj/item/inhaler/attack_self(mob/user, modifiers)
+ try_remove_canister(user, modifiers)
+
+ return ..()
+
+/obj/item/inhaler/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if (istype(tool, /obj/item/reagent_containers/inhaler_canister))
+ return try_insert_canister(tool, user, modifiers)
+
+ return ..()
+
+/// Tries to remove the canister, if any is inserted.
+/obj/item/inhaler/proc/try_remove_canister(mob/living/user, modifiers)
+ if (isnull(canister))
+ balloon_alert(user, "no canister inserted!")
+ return FALSE
+
+ if (canister.removal_time > 0)
+ balloon_alert(user, "removing canister...")
+ if (!do_after(user, canister.removal_time, src))
+ return FALSE
+
+ balloon_alert(user, "canister removed")
+ playsound(src, canister.post_insert_sound, canister.post_insert_volume)
+ set_canister(null, user)
+
+// Tries to insert a canister, if none is already inserted.
+/obj/item/inhaler/proc/try_insert_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, params)
+ if (!isnull(canister))
+ balloon_alert(user, "remove the existing canister!")
+ return FALSE
+
+ balloon_alert(user, "inserting canister...")
+ playsound(src, new_canister.pre_insert_sound, new_canister.pre_insert_volume)
+ if (!do_after(user, new_canister.insertion_time, src))
+ return FALSE
+ playsound(src, new_canister.post_insert_sound, new_canister.post_insert_volume)
+ balloon_alert(user, "canister inserted")
+ set_canister(new_canister, user)
+
+ return TRUE
+
+/// Setter proc for [canister]. Moves the existing canister out of the inhaler, while moving a new canister inside and registering it.
+/obj/item/inhaler/proc/set_canister(obj/item/reagent_containers/inhaler_canister/new_canister, mob/living/user, move_canister = TRUE)
+ if (move_canister && !isnull(canister))
+ if (iscarbon(loc))
+ var/mob/living/carbon/carbon_loc = loc
+ INVOKE_ASYNC(carbon_loc, TYPE_PROC_REF(/mob/living/carbon, put_in_hands), canister)
+ else if (!isnull(loc))
+ canister.forceMove(loc)
+
+ canister = new_canister
+ canister?.forceMove(src)
+ update_canister_underlay()
+
+/// Determines if we can be used. Fails on no canister, empty canister, invalid targets, or non-breathing targets.
+/obj/item/inhaler/proc/can_puff(mob/living/target_mob, mob/living/user, silent = FALSE)
+ if (isnull(canister))
+ if (!silent)
+ balloon_alert(user, "no canister!")
+ return FALSE
+ if (isnull(canister.reagents) || canister.reagents.total_volume <= 0)
+ if (!silent)
+ balloon_alert(user, "canister is empty!")
+ return FALSE
+ if (!iscarbon(target_mob)) // maybe mix this into a general has mouth check
+ if (!silent)
+ balloon_alert(user, "not breathing!")
+ return FALSE
+ var/mob/living/carbon/carbon_target = target_mob
+ if (carbon_target.is_mouth_covered())
+ if (!silent)
+ balloon_alert(user, "expose the mouth!")
+ return FALSE
+ if (HAS_TRAIT(carbon_target, TRAIT_NOBREATH))
+ if (!silent)
+ balloon_alert(user, "not breathing!")
+ return FALSE
+ var/obj/item/organ/lungs/lungs = carbon_target.get_organ_slot(ORGAN_SLOT_LUNGS)
+ if (isnull(lungs) || lungs.received_pressure_mult <= 0)
+ if (!silent)
+ balloon_alert(user, "not breathing!")
+ return FALSE
+
+ return TRUE
+
+/obj/item/reagent_containers/inhaler_canister
+ name = "inhaler canister"
+ desc = "A small canister filled with aerosolized reagents for use in a inhaler."
+ w_class = WEIGHT_CLASS_TINY
+
+ icon = 'icons/obj/medical/chemical.dmi'
+ icon_state = "canister_generic"
+
+ reagent_flags = SEALED_CONTAINER|DRAINABLE|REFILLABLE
+ has_variable_transfer_amount = FALSE
+
+ max_integrity = 60
+
+ custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.2)
+
+ /// The sound that plays when we are used.
+ var/puff_sound = 'sound/effects/spray.ogg'
+ /// The volume of [puff_sound]
+ var/puff_volume = 20
+
+ /// The sound that plays when someone TRIES to insert us.
+ var/pre_insert_sound = 'sound/items/taperecorder/tape_flip.ogg'
+ /// The sound that plays when we are removed or inserted.
+ var/post_insert_sound = 'sound/items/taperecorder/taperecorder_close.ogg'
+
+ /// The volume of [pre_insert_sound]
+ var/pre_insert_volume = 50
+ /// The volume of [post_insert_sound]
+ var/post_insert_volume = 50
+
+ /// The time it takes to insert us into a inhaler.
+ var/insertion_time = 2 SECONDS
+ /// The time it takes to remove us from a inhaler.
+ var/removal_time = 0.5 SECONDS
+
+ /// The time it takes for us to be used on someone else.
+ var/other_administer_delay = 3 SECONDS
+ /// The time it takes for us to be used on our owner.
+ var/self_administer_delay = 1 SECONDS
+
+/// Called when a inhaler we are in is used on someone. Transfers reagents and plays the puff sound.
+/obj/item/reagent_containers/inhaler_canister/proc/puff(mob/living/user, mob/living/carbon/target)
+ playsound(src, puff_sound, puff_volume, TRUE, -6)
+ reagents.trans_to(target, amount_per_transfer_from_this, transferred_by = user, methods = INHALE)
+
+/// Returns a integer approximating how many puffs we can be used for.
+/obj/item/reagent_containers/inhaler_canister/proc/get_puffs_left()
+ return ROUND_UP(reagents.total_volume / amount_per_transfer_from_this)
+
+/obj/item/reagent_containers/inhaler_canister/handle_deconstruct(disassembled)
+ if (!reagents?.total_volume)
+ return ..()
+
+ var/datum/reagents/smoke_reagents = new/datum/reagents() // Lets be safe first, our own reagents may be qdelled if we get deleted
+ var/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/smoke = new()
+ smoke_reagents.my_atom = src
+ for (var/datum/reagent/reagent as anything in reagents.reagent_list)
+ smoke_reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity)
+ reagents.remove_reagent(reagent.type, reagent.volume)
+ if (smoke_reagents.reagent_list)
+ smoke.set_up(1, holder = src, location = get_turf(src), carry = smoke_reagents)
+ smoke.start(log = TRUE)
+ visible_message(span_warning("[src] breaks open and sprays its aerosilized contents everywhere!"))
+ else
+ visible_message(span_warning("[src] breaks open - but is empty!"))
+
+ return ..()
+
+/obj/item/inhaler/medical
+ icon_state = "inhaler_medical"
+
+/obj/item/inhaler/salbutamol
+ name = "salbutamol inhaler"
+ icon_state = "inhaler_medical"
+ initial_casister_path = /obj/item/reagent_containers/inhaler_canister/salbutamol
+
+/obj/item/reagent_containers/inhaler_canister/salbutamol
+ name = "salbutamol canister"
+ icon_state = "canister_medical"
+ list_reagents = list(/datum/reagent/medicine/salbutamol = 30)
+
+/obj/item/inhaler/albuterol
+ name = "albuterol inhaler"
+ icon_state = "inhaler_medical"
+ initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol
+
+/obj/item/reagent_containers/inhaler_canister/albuterol
+ name = "albuterol canister"
+ desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \
+ asthma attacks in their tracks."
+ icon_state = "canister_medical"
+ list_reagents = list(/datum/reagent/medicine/albuterol = 30)
+
+/obj/item/reagent_containers/inhaler_canister/albuterol/asthma
+ name = "low-pressure albuterol canister"
+ desc = "A small canister filled with aerosolized reagents for use in a inhaler. This one contains albuterol, a potent bronchodilator that can stop \
+ asthma attacks in their tracks. It seems to be a lower-pressure variant, and can only hold 20u."
+ list_reagents = list(/datum/reagent/medicine/albuterol = 20)
+ volume = 20
+
+/obj/item/inhaler/albuterol/asthma
+ name = "rescue inhaler"
+ icon_state = "inhaler_generic"
+ initial_casister_path = /obj/item/reagent_containers/inhaler_canister/albuterol/asthma
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index 9cfab3b3f8a..e4a8b78a6e7 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -128,6 +128,30 @@
)
departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE
+/datum/design/inhaler
+ name = "Inhaler"
+ desc = "A small device capable of administering short bursts of aerosolized chemicals. Requires a canister to function."
+ id = "inhaler"
+ build_path = /obj/item/inhaler/medical
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 0.1)
+ category = list(
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
+
+/datum/design/inhaler_canister
+ name = "Inhaler Canister"
+ desc = "A small canister filled with aerosolized reagents for use in a inhaler."
+ id = "inhaler_canister"
+ build_path = /obj/item/reagent_containers/inhaler_canister
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.2)
+ category = list(
+ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MEDICAL
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_MEDICAL
+
/datum/design/bluespacebodybag
name = "Bluespace Body Bag"
desc = "A bluespace body bag, powered by experimental bluespace technology. It can hold loads of bodies and the largest of creatures."
diff --git a/code/modules/research/techweb/nodes/medbay_nodes.dm b/code/modules/research/techweb/nodes/medbay_nodes.dm
index 0e16f9cacbd..49d4aad71f7 100644
--- a/code/modules/research/techweb/nodes/medbay_nodes.dm
+++ b/code/modules/research/techweb/nodes/medbay_nodes.dm
@@ -49,6 +49,8 @@
prereq_ids = list(TECHWEB_NODE_MEDBAY_EQUIP)
design_ids = list(
"med_spray_bottle",
+ "inhaler",
+ "inhaler_canister",
"medigel",
"medipen_refiller",
"soda_dispenser",
diff --git a/code/modules/surgery/asthmatic_bypass.dm b/code/modules/surgery/asthmatic_bypass.dm
new file mode 100644
index 00000000000..7e69c99a7bd
--- /dev/null
+++ b/code/modules/surgery/asthmatic_bypass.dm
@@ -0,0 +1,96 @@
+/datum/surgery/asthmatic_bypass
+ name = "Asthmatic Bypass"
+ surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB
+ requires_bodypart_type = NONE
+ organ_to_manipulate = ORGAN_SLOT_LUNGS
+ possible_locs = list(BODY_ZONE_CHEST)
+ steps = list(
+ /datum/surgery_step/incise,
+ /datum/surgery_step/retract_skin,
+ /datum/surgery_step/clamp_bleeders,
+ /datum/surgery_step/incise,
+ /datum/surgery_step/expand_windpipe,
+ /datum/surgery_step/close,
+ )
+
+/datum/surgery/asthmatic_bypass/can_start(mob/user, mob/living/patient)
+ . = ..()
+
+ if (!.)
+ return
+
+ return (patient.has_quirk(/datum/quirk/item_quirk/asthma))
+
+/datum/surgery_step/expand_windpipe
+ name = "force open windpipe (retractor)"
+ implements = list(
+ TOOL_RETRACTOR = 80,
+ TOOL_WIRECUTTER = 45,
+ )
+ time = 8 SECONDS
+ repeatable = TRUE
+ preop_sound = 'sound/items/handling/surgery/retractor1.ogg'
+ success_sound = 'sound/items/handling/surgery/retractor2.ogg'
+
+ /// The amount of inflammation a failure or success of this surgery will reduce.
+ var/inflammation_reduction = 75
+
+/datum/surgery_step/expand_windpipe/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ display_results(
+ user,
+ target,
+ span_notice("You start to stretch [target]'s windpipe, trying your best to avoid nearby blood vessels..."),
+ span_notice("[user] begins to stretch [target]'s windpipe, taking care to avoid any nearby blood vessels."),
+ span_notice("[user] begins to stretch [target]'s windpipe."),
+ )
+ display_pain(target, "You feel an agonizing stretching sensation in your neck!")
+
+/datum/surgery_step/expand_windpipe/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = TRUE)
+ if (!reduce_inflammation(user, target, tool, surgery))
+ return
+
+ default_display_results = FALSE
+ display_results(
+ user,
+ target,
+ span_notice("You stretch [target]'s windpipe with [tool], managing to avoid the nearby blood vessels and arteries."),
+ span_notice("[user] succeeds at stretching [target]'s windpipe with [tool], avoiding the nearby blood vessels and arteries."),
+ span_notice("[user] finishes stretching [target]'s windpipe.")
+ )
+
+ return ..()
+
+/datum/surgery_step/expand_windpipe/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob)
+ if (!reduce_inflammation(user, target, tool, surgery))
+ return
+
+ display_results(
+ user,
+ target,
+ span_bolddanger("You stretch [target]'s windpipe with [tool], but accidentally clip a few arteries!"),
+ span_bolddanger("[user] succeeds at stretching [target]'s windpipe with [tool], but accidentally clips a few arteries!"),
+ span_bolddanger("[user] finishes stretching [target]'s windpipe, but screws up!")
+ )
+
+ target.losebreath++
+
+ if (iscarbon(target))
+ var/mob/living/carbon/carbon_patient = target
+ var/wound_bonus = tool.wound_bonus
+ var/obj/item/bodypart/head/patient_chest = carbon_patient.get_bodypart(BODY_ZONE_CHEST)
+ if (patient_chest)
+ if (prob(30))
+ carbon_patient.cause_wound_of_type_and_severity(WOUND_SLASH, patient_chest, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL, WOUND_PICK_LOWEST_SEVERITY, tool)
+ patient_chest.receive_damage(brute = 10, wound_bonus = wound_bonus, sharpness = SHARP_EDGED, damage_source = tool)
+
+ return FALSE
+
+/// Reduces the asthmatic's inflammation by [inflammation_reduction]. Called by both success and failure.
+/datum/surgery_step/expand_windpipe/proc/reduce_inflammation(mob/user, mob/living/target, obj/item/tool, datum/surgery/surgery)
+ var/datum/quirk/item_quirk/asthma/asthma_quirk = locate(/datum/quirk/item_quirk/asthma) in target.quirks
+ if (isnull(asthma_quirk))
+ qdel(surgery) // not really an error cause quirks can get removed during surgery?
+ return FALSE
+
+ asthma_quirk.adjust_inflammation(-inflammation_reduction)
+ return TRUE
diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm
index ac602413eee..54de85f5467 100644
--- a/code/modules/surgery/organs/_organ.dm
+++ b/code/modules/surgery/organs/_organ.dm
@@ -324,6 +324,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
lungs = new()
lungs.Insert(src)
lungs.set_organ_damage(0)
+ lungs.received_pressure_mult = lungs::received_pressure_mult
var/obj/item/organ/heart/heart = get_organ_slot(ORGAN_SLOT_HEART)
if(heart)
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index 27a88a6d94e..c15dd74a263 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -69,6 +69,9 @@
var/n2o_euphoria = EUPHORIA_LAST_FLAG
var/healium_euphoria = EUPHORIA_LAST_FLAG
+ /// All incoming breaths will have their pressure multiplied against this. Higher values allow more air to be breathed at once,
+ /// while lower values can cause suffocation in low pressure environments.
+ var/received_pressure_mult = 1
var/oxy_breath_dam_min = MIN_TOXIC_GAS_DAMAGE
var/oxy_breath_dam_max = MAX_TOXIC_GAS_DAMAGE
@@ -168,6 +171,7 @@
receiver.clear_alert(ALERT_NOT_ENOUGH_NITRO)
receiver.clear_alert(ALERT_NOT_ENOUGH_PLASMA)
receiver.clear_alert(ALERT_NOT_ENOUGH_N2O)
+ update_bronchodilation_alerts()
/obj/item/organ/lungs/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
. = ..()
@@ -646,7 +650,7 @@
// 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])
+ partial_pressures[gas_id] = breath.get_breath_partial_pressure(breath_gases[gas_id][MOLES] * received_pressure_mult)
// Treat gas as other types of gas
for(var/list/conversion_packet in treat_as)
@@ -1044,6 +1048,38 @@
#undef GAS_TOLERANCE
+/// Adjusting proc for [received_pressure_mult]. Updates bronchodilation alerts.
+/obj/item/organ/lungs/proc/adjust_received_pressure_mult(adjustment)
+ received_pressure_mult = max(received_pressure_mult + adjustment, 0)
+ update_bronchodilation_alerts()
+
+/// Setter proc for [received_pressure_mult]. Updates bronchodilation alerts.
+/obj/item/organ/lungs/proc/set_received_pressure_mult(new_value)
+ received_pressure_mult = max(new_value, 0)
+ update_bronchodilation_alerts()
+
+#define LUNG_CAPACITY_ALERT_BUFFER 0.003
+/// Depending on [received_pressure_mult], gives either a bronchocontraction or bronchoconstriction alert to our owner (if we have one), or clears the alert
+/// if [received_pressure_mult] is near 1.
+/obj/item/organ/lungs/proc/update_bronchodilation_alerts()
+ if (!owner)
+ return
+
+ var/initial_value = initial(received_pressure_mult)
+
+ // you wont really notice if youre only breathing a bit more or a bit less
+ var/dilated = (received_pressure_mult > (initial_value + LUNG_CAPACITY_ALERT_BUFFER))
+ var/constricted = (received_pressure_mult < (initial_value - LUNG_CAPACITY_ALERT_BUFFER))
+
+ if (dilated)
+ owner.throw_alert(ALERT_BRONCHODILATION, /atom/movable/screen/alert/bronchodilated)
+ else if (constricted)
+ owner.throw_alert(ALERT_BRONCHODILATION, /atom/movable/screen/alert/bronchoconstricted)
+ else
+ owner.clear_alert(ALERT_BRONCHODILATION)
+
+#undef LUNG_CAPACITY_ALERT_BUFFER
+
/obj/item/organ/lungs/ethereal
name = "aeration reticulum"
desc = "These exotic lungs seem crunchier than most."
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index 21cabf95dd3..c94bc440663 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -103,6 +103,7 @@
/obj/item/reagent_containers/medigel/synthflesh = 2,
/obj/item/storage/pill_bottle/psicodine = 2,
/obj/item/storage/pill_bottle/sansufentanyl = 1,
+ /obj/item/inhaler/albuterol = 2,
)
default_price = 50
extra_price = 100
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index d308aea2e2f77fece514248a5a09cc5fbe0b5af6..58aa910cb2248d65d2c1c9b9140c7280f1a8ad3c 100644
GIT binary patch
delta 25952
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≤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