diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm
index aa7f6b8fe2fe..60d35c797ccd 100644
--- a/code/__DEFINES/living.dm
+++ b/code/__DEFINES/living.dm
@@ -113,10 +113,18 @@
/// Doesn't let a mob shift this atom around with move_pulled
#define TRAIT_NO_MOVE_PULL "no_move_pull"
-/// Boosts the heart rate of the mob
+/// Boosts the heart rate of the mob (raises blood pressure)
+/// One application of the trait translates to +10 bpm, which may translate to +10 blood pressure
#define TRAIT_HEART_RATE_BOOST "heart_rate_boost"
-/// Slows the heart rate of the mob
+/// Slows the heart rate of the mob (lowers blood pressure)
+/// One application of the trait translates to -10 bpm, which may translate to -10 blood pressure
#define TRAIT_HEART_RATE_SLOW "heart_rate_slow"
+/// Constricts blood vessels (raises blood pressure)
+/// One application of the trait translates to +0.2 "vasoconstriction", which is a +0.2 multiplier to blood pressure
+#define TRAIT_VASOCONSTRICTED "vasoconstricted"
+/// Dilates blood vessels (lowers blood pressure)
+/// One application of the trait translates to -0.2 "vasodilation", which is a -0.2 multiplier to blood pressure
+#define TRAIT_VASODILATED "vasodilated"
/// The trait that determines if someone has the robotic limb reattachment quirk.
#define TRAIT_ROBOTIC_LIMBATTACHMENT "trait_robotic_limbattachment"
@@ -179,9 +187,9 @@
#define UPDATE_SELF (UPDATE_SELF_DAMAGE | UPDATE_SELF_HEALTH)
/// Threshold that heart beat becomes "slow"
-#define SLOW_HEARTBEAT_THRESHOLD 6
+#define SLOW_HEARTBEAT_THRESHOLD 60
/// Threshold that heart beat becomes "fast"
-#define FAST_HEARTBEAT_THRESHOLD 11
+#define FAST_HEARTBEAT_THRESHOLD 110
// Used in living mob offset list for determining pixel offsets
#define PIXEL_W_OFFSET "w"
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index d4077a3d1d0a..9e263a1ecfca 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -239,10 +239,12 @@
/datum/status_effect/exercised/on_apply()
owner.add_mood_event("exercise", /datum/mood_event/exercise, owner.mind.get_skill_level(/datum/skill/athletics))
+ ADD_TRAIT(owner, TRAIT_HEART_RATE_BOOST, type)
return ..()
/datum/status_effect/exercised/on_remove()
owner.clear_mood_event("exercise")
+ REMOVE_TRAIT(owner, TRAIT_HEART_RATE_BOOST, type)
/atom/movable/screen/alert/status_effect/exercised
name = "Exercise"
diff --git a/code/datums/status_effects/debuffs/grabbed.dm b/code/datums/status_effects/debuffs/grabbed.dm
index e52ca414ffe3..35d4374dcdf6 100644
--- a/code/datums/status_effects/debuffs/grabbed.dm
+++ b/code/datums/status_effects/debuffs/grabbed.dm
@@ -634,12 +634,6 @@
#undef CRIT_SOURCE
#undef PIN_SOURCE
-#define IS_VULNERABLE(mob) (\
- mob.incapacitated(IGNORE_GRAB|IGNORE_STASIS) \
- || mob.body_position == LYING_DOWN \
- || (mob.has_status_effect(/datum/status_effect/staggered) && mob.getStaminaLoss() >= 30) \
-)
-
/**
* Checks how strong our grabs are.
*
@@ -647,7 +641,9 @@
* somewhere in the range of 0-10 for a normal human.
*/
/atom/movable/proc/get_grab_strength()
+ // resist strength and grab strength overlap in plenty of ways so we will re-use it for our base
. = get_grab_resist_strength()
+ // strength adds a bonus on top of resist strength (stacking with the strengh bonus already factored in to resistance)
if(HAS_TRAIT(src, TRAIT_STRENGTH))
. += 1
@@ -686,28 +682,41 @@
return 5
/mob/living/get_grab_resist_strength()
+ // base resistance is base on size
. += mob_size * 2
+ // athletics skill can give a decent boost
. += clamp(0.5 * ((mind?.get_skill_level(/datum/skill/athletics) || 1) - 2), -1, 3)
+ // monkey wrangling buff
if(ismonkey(src))
. -= 1
+ // dead people are not actively resisting, though this is not an absurdly high nubmer like -20
+ // because grabbing a corpse should still be cumbersome and unwieldy
if(stat == DEAD)
. -= 4
- else if(IS_VULNERABLE(src))
+ // otherwise being in soft crit or incapacitated means you can't fight back well
+ else if(HAS_TRAIT(src, TRAIT_SOFT_CRIT) || incapacitated(IGNORE_GRAB|IGNORE_STASIS))
. -= 2
- if(HAS_TRAIT(src, TRAIT_STRENGTH))
- . += 1
+ // lying down or staggered represents a vulnerable position which has you disadvanteged
+ if(body_position == LYING_DOWN || (has_status_effect(/datum/status_effect/staggered) && getStaminaLoss() >= 30))
+ . -= 2
+ // generic grab weakness such as from quirks
if(HAS_TRAIT(src, TRAIT_GRABWEAKNESS))
. -= 2
- // these two are not
+ // grabs you grabs you grabs you
if(HAS_TRAIT(src, TRAIT_SMALL))
. -= 2
+
+ // generic buff
+ if(HAS_TRAIT(src, TRAIT_STRENGTH))
+ . += 1
+ // hulks are the pinnacle of strength
if(HAS_TRAIT(src, TRAIT_HULK))
. += 3
+ // being large also helps considerably
+ // mutually exclusive with hulk as hulk's strength can be interpreted as coming from size
else if(HAS_TRAIT(src, TRAIT_GIANT) || HAS_TRAIT(src, TRAIT_HUGE))
. += 2
-#undef IS_VULNERABLE
-
/**
* When given a base climb speed, modifies it based on how good a climber we are.
*
diff --git a/code/datums/status_effects/drug_effects.dm b/code/datums/status_effects/drug_effects.dm
index fa2376714c93..30d79ca657a9 100644
--- a/code/datums/status_effects/drug_effects.dm
+++ b/code/datums/status_effects/drug_effects.dm
@@ -12,36 +12,6 @@
desc = "You feel a bit slower than usual, it seems doing things with your hands takes longer than it usually does."
icon_state = "woozy"
-/datum/status_effect/high_blood_pressure
- id = "high_blood_pressure"
- tick_interval = -1
- status_type = STATUS_EFFECT_UNIQUE
- alert_type = null
-
-/datum/status_effect/high_blood_pressure/on_apply()
- if(!ishuman(owner))
- return FALSE
-
- var/mob/living/carbon/human/human_owner = owner
- human_owner.physiology.bleed_mod *= 1.25
- RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_healthscan))
- return TRUE
-
-/datum/status_effect/high_blood_pressure/on_remove()
- if(!ishuman(owner))
- return
-
- var/mob/living/carbon/human/human_owner = owner
- human_owner.physiology.bleed_mod /= 1.25
- UnregisterSignal(owner, COMSIG_LIVING_HEALTHSCAN)
-
-/datum/status_effect/high_blood_pressure/proc/on_healthscan(datum/source, list/render_list, advanced, mob/user, mode, tochat)
- SIGNAL_HANDLER
-
- if(owner.has_status_effect(/datum/status_effect/low_blood_pressure))
- return
- render_list += "Hypertension detected.
"
-
/datum/status_effect/seizure
id = "seizure"
tick_interval = -1
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 8096ff5d9fac..e4bc46685fb2 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -372,18 +372,25 @@
// Blood Level
var/datum/blood_type/target_blood_type = target.get_blood_type()
if(target_blood_type)
- var/bpm = target.get_bpm()
+ var/list/bp = target.get_bp_range()
var/needs_heart = TRUE
if(ishuman(target))
var/mob/living/carbon/human/humantarget = target
needs_heart = humantarget.needs_heart()
- var/bpm_format = "[needs_heart ? bpm : "n/a"] bpm"
- var/level_format = "[round_and_format_decimal(target.blood_volume, 0.1)] cl" // round to 0.1 but also print "100.0" and not "100"
+ var/bp_format = "[bp[1]]/[bp[2]]" + span_slightly_smaller("mmHg")
+ var/level_format = "[round_and_format_decimal(target.blood_volume, 0.1)]" + span_slightly_smaller("cl") // round to 0.1 but also print "100.0" and not "100"
var/blood_type_format = "[target_blood_type.name]"
- if(needs_heart && (bpm < 60 || bpm > 100))
- bpm_format = span_alert(bpm_format)
+ if(needs_heart)
+ if(bp[1] > 140 && bp[2] > 90) // high blood pressure
+ bp_format = conditional_tooltip(span_alert("[bp_format] (Warning: Hypertension)"), \
+ "To fix, resolve underlying causes such as pain, hypoxima, or blood loss. Side effects can be abated by \
+ supplying vasodilators such as [/datum/reagent/potassium::name] or [/datum/reagent/nitroglycerin::name].", tochat)
+ if(bp[1] < 90 && bp[2] < 60) // low blood pressure
+ bp_format = conditional_tooltip(span_alert("[bp_format] (Warning: Hypotension)"), \
+ "To fix, resolve underlying causes such as heart damage or blood loss. Side effects can be abated by \
+ supplying vasoconstrictors such as [/datum/reagent/medicine/epinephrine::name].", tochat)
switch(target.blood_volume)
if(BLOOD_VOLUME_EXCESS to INFINITY)
@@ -410,7 +417,7 @@
recieve_from_text += " Regenerates slowly via [target_blood_type.restoration_chem::name] reagent."
blood_type_format = span_tooltip(recieve_from_text, blood_type_format)
- render_list += "Heart rate: [bpm_format]
"
+ render_list += "Blood pressure: [bp_format]
"
render_list += "Blood level: [level_format]
"
render_list += "Blood type: [blood_type_format]
"
diff --git a/code/game/objects/items/robot/items/hypo.dm b/code/game/objects/items/robot/items/hypo.dm
index a70ffd25349b..0b742464d66f 100644
--- a/code/game/objects/items/robot/items/hypo.dm
+++ b/code/game/objects/items/robot/items/hypo.dm
@@ -36,7 +36,7 @@
)
#define HACKED_PEACE_REAGENTS list(\
/datum/reagent/toxin/cyanide,\
- /datum/reagent/toxin/fentanyl,\
+ /datum/reagent/medicine/painkiller/fentanyl,\
/datum/reagent/toxin/sodium_thiopental,\
/datum/reagent/toxin/staminatoxin,\
/datum/reagent/toxin/sulfonal\
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index c93b6cc55143..a0eda225b48e 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -307,7 +307,7 @@
//assess heart
if(body_part == BODY_ZONE_CHEST)//if we're listening to the chest
- var/heart_rate = carbon_patient.get_heart_rate()
+ var/heart_rate = carbon_patient.get_bpm()
switch(heart_rate)
if(0)
render_list += "You don't hear a heartbeat!\n"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 47bd0c95ae10..04c0969eb7bf 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -548,39 +548,46 @@
SShealth_updates.queue_update(src, UPDATE_SELF|UPDATE_MEDHUD_HEALTH)
/mob/living/carbon/proc/paincrit_check()
- if(crit_percent() < 100 || HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) // melbert todo
- if(HAS_TRAIT_FROM(src, TRAIT_SOFT_CRIT, PAINCRIT))
- Paralyze(2 SECONDS)
- remove_traits(list(TRAIT_SOFT_CRIT, TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED, TRAIT_HANDS_BLOCKED), PAINCRIT)
+ if(crit_percent() < 100)
+ remove_status_effect(/datum/status_effect/paincrit)
return
- if(HAS_TRAIT_FROM(src, TRAIT_SOFT_CRIT, PAINCRIT))
- return
- var/is_standing = body_position == STANDING_UP
- add_traits(list(TRAIT_SOFT_CRIT, TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED, TRAIT_HANDS_BLOCKED), PAINCRIT)
- if(stat == DEAD)
- return
- if(buckled)
- visible_message(
- span_warning("[src] slumps against [buckled]!"),
- span_userdanger("You go limp, unable to move!"),
- visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
- )
+ apply_status_effect(/datum/status_effect/paincrit)
- else if(is_standing && body_position != STANDING_UP)
- visible_message(
- span_warning("[src] collapses!"),
- span_userdanger("You collapse, unable to stand!"),
- visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
- )
- else if(body_position == LYING_DOWN)
- visible_message(
- span_warning("[src] slumps against the ground!"),
- span_userdanger("You go limp, unable to get up!"),
- visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
- )
- else
- to_chat(src, span_userdanger("You can't will yourself to move!"))
+/datum/status_effect/paincrit
+ id = "paincrit"
+ alert_type = null
+ tick_interval = -1
+ duration = -1
+
+/datum/status_effect/paincrit/nextmove_modifier()
+ return 3
+
+/datum/status_effect/paincrit/on_apply()
+ . = ..()
+ owner.add_traits(list(TRAIT_SOFT_CRIT, TRAIT_FLOORED, TRAIT_GRABWEAKNESS), id)
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/paincrit)
+ owner.add_actionspeed_modifier(/datum/actionspeed_modifier/paincrit)
+ owner.drop_all_held_items()
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOSOFTCRIT), PROC_REF(clean_up))
+
+/datum/status_effect/paincrit/on_remove()
+ . = ..()
+ owner.remove_traits(list(TRAIT_SOFT_CRIT, TRAIT_FLOORED, TRAIT_GRABWEAKNESS), id)
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/paincrit)
+ owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/paincrit)
+ owner.Paralyze(2 SECONDS)
+ UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOSOFTCRIT))
+
+/datum/status_effect/paincrit/clean_up()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/movespeed_modifier/paincrit
+ multiplicative_slowdown = 9
+
+/datum/actionspeed_modifier/paincrit
+ multiplicative_slowdown = 3
/mob/living/carbon/update_sight()
if(!client)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index e500632e7dc2..9be64018b8f0 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -81,6 +81,8 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift)
if(!HAS_TRAIT(src, TRAIT_NOBLOOD) && blood_volume < BLOOD_VOLUME_BAD)
return BLOOD_LOSS
+ return "hypoxia"
+
if(TOX_DAMAGE)
var/obj/item/organ/liver/liver = get_organ_slot(ORGAN_SLOT_LIVER)
if(isnull(liver) || (liver.organ_flags & ORGAN_FAILING))
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index b21798fdef92..e949271296ea 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -66,7 +66,7 @@
if(stat == HARD_CRIT && !internal && !external) // being on internals function as a ventilator + also makes anesthetic function (revisit later)
losebreath = max(losebreath, 1)
else if(HAS_TRAIT(src, TRAIT_LABOURED_BREATHING))
- losebreath += (1 / next_breath)
+ losebreath += (1 / max(2, next_breath))
if(losebreath < 1)
var/pre_sig_return = SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE, seconds_per_tick, times_fired)
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 6481efb8c75b..e91b8b807db3 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -176,9 +176,11 @@
if(feels_like > hot_threshold_high)
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 3)
add_mood_event("hot", /datum/mood_event/overhot)
+ ADD_TRAIT(src, TRAIT_VASODILATED, "extreme_temperature")
else if(feels_like > hot_threshold_medium)
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 2)
add_mood_event("hot", /datum/mood_event/hot)
+ ADD_TRAIT(src, TRAIT_VASODILATED, "high_temperature")
else
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, 1)
add_mood_event("hot", /datum/mood_event/warm)
@@ -194,9 +196,11 @@
if(feels_like < cold_threshold_high)
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 3)
add_mood_event("cold", /datum/mood_event/freezing)
+ ADD_TRAIT(src, TRAIT_VASOCONSTRICTED, "extreme_temperature")
else if(feels_like < cold_threshold_medium)
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 2)
add_mood_event("cold", /datum/mood_event/cold)
+ ADD_TRAIT(src, TRAIT_VASOCONSTRICTED, "high_temperature")
else
throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 1)
add_mood_event("cold", /datum/mood_event/chilly)
@@ -213,6 +217,8 @@
clear_mood_event("cold")
clear_mood_event("hot")
temp_alerts = FALSE
+ REMOVE_TRAITS_IN(src, "extreme_temperature")
+ REMOVE_TRAITS_IN(src, "high_temperature")
/mob/living/silicon/body_temperature_alerts()
return // Not yet
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index bf11c7d9cf4b..0cab70c31ff2 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1486,7 +1486,7 @@
if(!istype(target))
CRASH("Missing target arg for can_perform_action")
- if(stat != CONSCIOUS)
+ if(stat >= UNCONSCIOUS)
to_chat(src, span_warning("You are not conscious enough for this action!"))
return FALSE
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 69c858fc7eea..bcd8fc48c9ce 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -233,8 +233,6 @@ Primarily used in reagents/reaction_agents
/// Called when an overdose starts. Returning UPDATE_MOB_HEALTH will cause updatehealth() to be called on the holder mob by /datum/reagents/proc/metabolize.
/datum/reagent/proc/overdose_start(mob/living/affected_mob)
- to_chat(affected_mob, span_userdanger("You feel like you took too much of [name]!"))
- affected_mob.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name)
return
/**
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index e531518b2c99..a702ee6f7158 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -13,6 +13,12 @@
if(trippy)
affected_mob.clear_mood_event("[type]_high")
REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, type)
+ REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_overdose")
+
+/datum/reagent/drug/overdose_start(mob/living/affected_mob)
+ . = ..()
+ affected_mob.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name)
+ ADD_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_overdose")
/datum/reagent/drug/space_drugs
name = "Space Drugs"
@@ -35,7 +41,6 @@
/datum/reagent/drug/space_drugs/overdose_start(mob/living/affected_mob)
. = ..()
to_chat(affected_mob, span_userdanger("You start tripping hard!"))
- affected_mob.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name)
/datum/reagent/drug/space_drugs/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 64ad54e1636c..d53c1a744754 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -990,12 +990,14 @@
ADD_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
ADD_TRAIT(affected_mob, TRAIT_ABATES_SHOCK, type)
ADD_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
+ ADD_TRAIT(affected_mob, TRAIT_VASOCONSTRICTED, type)
/datum/reagent/medicine/epinephrine/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
REMOVE_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
REMOVE_TRAIT(affected_mob, TRAIT_ABATES_SHOCK, type)
REMOVE_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
+ REMOVE_TRAIT(affected_mob, TRAIT_VASOCONSTRICTED, type)
/datum/reagent/medicine/epinephrine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 0164426ec630..5e2d743ae33b 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1014,7 +1014,7 @@
/datum/reagent/potassium
name = "Potassium"
description = "A soft, low-melting solid that can easily be cut with a knife. Reacts violently with water. \
- Often used medicinally to treat low blood pressure, but can be dangerous in high doses."
+ Often used medicinally to treat high blood pressure, but can be dangerous in high doses."
reagent_state = SOLID
color = "#A0A0A0" // rgb: 160, 160, 160
taste_description = "sweetness"
@@ -1023,14 +1023,14 @@
/datum/reagent/potassium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(volume > 10)
- ADD_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_low")
+ ADD_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_low")
if(volume > 20)
- ADD_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_high")
+ ADD_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_high")
/datum/reagent/potassium/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_low")
- REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, "[type]_high")
+ REMOVE_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_low")
+ REMOVE_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_high")
/datum/reagent/mercury
name = "Mercury"
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index 702d31269450..a5a3c00a4529 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -29,14 +29,15 @@
. = ..()
if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags))
return UPDATE_MOB_HEALTH
-
-/datum/reagent/nitroglycerin/on_mob_metabolize(mob/living/carbon/user)
- . = ..()
- ADD_TRAIT(user, TRAIT_HEART_RATE_BOOST, type)
+ if(volume > 5)
+ ADD_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_low")
+ if(volume > 10)
+ ADD_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_high")
/datum/reagent/nitroglycerin/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_BOOST, type)
+ REMOVE_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_low")
+ REMOVE_TRAIT(affected_mob, TRAIT_VASODILATED, "[type]_high")
/datum/reagent/stabilizing_agent
name = "Stabilizing Agent"
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index ce7e484a04b4..068ab701e295 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -707,32 +707,43 @@
affected_mob.update_transform(RESIZE_DEFAULT_SIZE/current_size)
current_size = RESIZE_DEFAULT_SIZE
-/datum/reagent/toxin/fentanyl
+/datum/reagent/medicine/painkiller/fentanyl
name = "Fentanyl"
- description = "Inhibits brain function and causes toxin damage before eventually knocking out the patient."
+ description = "An incredibly potent painkiller used to treat extreme pain. \
+ It has a high potential for brain damage and addiction, and should be used with caution."
reagent_state = LIQUID
color = "#64916E"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
- toxpwr = 0
ph = 9
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/opioids = 25)
pain_modifier = 0.5
+ overdose_threshold = 10
-/datum/reagent/toxin/fentanyl/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+/datum/reagent/medicine/painkiller/fentanyl/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
- var/need_mob_update
- need_mob_update = affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * normalise_creation_purity() * seconds_per_tick, 150)
- if(affected_mob.toxloss <= 60)
- need_mob_update += affected_mob.adjustToxLoss(1 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)
- if(current_cycle > 4)
+ if(affected_mob.getToxLoss() <= 60 && (overdosed || SPT_PROB(current_cycle / 5, seconds_per_tick))) // 1% chance per second per 1u
+ affected_mob.adjustToxLoss(1 * REM * normalise_creation_purity() * seconds_per_tick, required_biotype = affected_biotype)
+
+ affected_mob.heal_pain(8 * REM * normalise_creation_purity() * seconds_per_tick)
+ if(current_cycle > 5) // 1u
affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy, name)
- if(current_cycle > 18)
- affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick)
- if(need_mob_update)
- return UPDATE_MOB_HEALTH
+ ADD_TRAIT(affected_mob, TRAIT_HEART_RATE_SLOW, type)
+ if(current_cycle > 20) // 4u
+ affected_mob.adjust_drowsiness_up_to(4 SECONDS * REM * normalise_creation_purity() * seconds_per_tick, 24 SECONDS)
+ if(current_cycle > 30 || overdosed) // 6u
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, clamp((current_cycle - 20) / 10, 1, 4) * REM * normalise_creation_purity() * seconds_per_tick, 150)
+
+/datum/reagent/medicine/painkiller/fentanyl/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+ REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_SLOW, type)
+
+/datum/reagent/medicine/painkiller/fentanyl/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ if(current_cycle > 10)
+ affected_mob.Sleeping(4 SECONDS * REM * normalise_creation_purity() * seconds_per_tick)
/datum/reagent/toxin/cyanide
name = "Cyanide"
@@ -901,14 +912,6 @@
if(current_cycle > 22)
affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick)
-/datum/reagent/nitroglycerin/on_mob_metabolize(mob/living/carbon/user)
- . = ..()
- ADD_TRAIT(user, TRAIT_HEART_RATE_SLOW, type)
-
-/datum/reagent/nitroglycerin/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_SLOW, type)
-
/datum/reagent/toxin/amanitin
name = "Amanitin"
description = "A very powerful delayed toxin. Upon full metabolization, a massive amount of toxin damage will be dealt depending on how long it has been in the victim's bloodstream."
diff --git a/code/modules/reagents/chemistry/recipes/toxins.dm b/code/modules/reagents/chemistry/recipes/toxins.dm
index b13b6e4a581e..e279177de674 100644
--- a/code/modules/reagents/chemistry/recipes/toxins.dm
+++ b/code/modules/reagents/chemistry/recipes/toxins.dm
@@ -20,7 +20,7 @@
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_DAMAGING | REACTION_TAG_CHEMICAL | REACTION_TAG_BRUTE | REACTION_TAG_TOXIN | REACTION_TAG_ORGAN | REACTION_TAG_OTHER
/datum/chemical_reaction/fentanyl
- results = list(/datum/reagent/toxin/fentanyl = 1)
+ results = list(/datum/reagent/medicine/painkiller/fentanyl = 1)
required_reagents = list(/datum/reagent/drug/space_drugs = 1)
mix_message = "The mixture turns cloudy, then becomes clear again."
is_cold_recipe = FALSE
@@ -328,7 +328,7 @@
/datum/chemical_reaction/rotatium
results = list(/datum/reagent/toxin/rotatium = 3)
- required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/teslium = 1, /datum/reagent/toxin/fentanyl = 1)
+ required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/teslium = 1, /datum/reagent/medicine/painkiller/fentanyl = 1)
mix_message = "After sparks, fire, and the smell of mindbreaker, the mix is constantly spinning with no stop in sight."
is_cold_recipe = FALSE
required_temp = 100
diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm
index 5d8ea25b7d2b..59752a5a7b65 100644
--- a/code/modules/reagents/reagent_containers/cups/bottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/bottle.dm
@@ -140,7 +140,7 @@
/obj/item/reagent_containers/cup/bottle/traitor/Initialize(mapload)
. = ..()
- extra_reagent = pick(/datum/reagent/toxin/polonium, /datum/reagent/toxin/histamine, /datum/reagent/toxin/formaldehyde, /datum/reagent/toxin/venom, /datum/reagent/toxin/fentanyl, /datum/reagent/toxin/cyanide)
+ extra_reagent = pick(/datum/reagent/toxin/polonium, /datum/reagent/toxin/histamine, /datum/reagent/toxin/formaldehyde, /datum/reagent/toxin/venom, /datum/reagent/medicine/painkiller/fentanyl, /datum/reagent/toxin/cyanide)
reagents.add_reagent(extra_reagent, 3)
/obj/item/reagent_containers/cup/bottle/polonium
@@ -161,7 +161,7 @@
/obj/item/reagent_containers/cup/bottle/fentanyl
name = "fentanyl bottle"
desc = "A small bottle. Contains Fentanyl."
- list_reagents = list(/datum/reagent/toxin/fentanyl = 30)
+ list_reagents = list(/datum/reagent/medicine/painkiller/fentanyl = 30)
/obj/item/reagent_containers/cup/bottle/formaldehyde
name = "formaldehyde bottle"
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 2264a9a5be98..e5e9c0190e9f 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -33,6 +33,8 @@
return FALSE
if(!iscarbon(affected_mob))
return FALSE
+ if(!pre_inject(affected_mob, user))
+ return FALSE
//Always log attemped injects for admins
var/list/injected = list()
@@ -61,6 +63,17 @@
return TRUE
return FALSE
+/obj/item/reagent_containers/hypospray/proc/pre_inject(mob/living/affected_mob, mob/user)
+ if(user.stat >= SOFT_CRIT)
+ user.visible_message(
+ span_warning("[user] struggles to inject [src] into [affected_mob == user ? user.p_themselves() : affected_mob]..."),
+ span_warning("You struggle with the cap of [src], trying to inject [affected_mob == user ? "yourself" : affected_mob]..."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
+ )
+ if(!do_after(user, 2.5 SECONDS, affected_mob))
+ return FALSE
+ return TRUE
/obj/item/reagent_containers/hypospray/cmo
volume = 60
@@ -263,18 +276,18 @@
amount_per_transfer_from_this = 30
list_reagents = list( /datum/reagent/medicine/epinephrine = 8, /datum/reagent/medicine/c2/aiuri = 8, /datum/reagent/medicine/c2/libital = 8, /datum/reagent/medicine/leporazine = 6)
-/obj/item/reagent_containers/hypospray/medipen/survival/inject(mob/living/affected_mob, mob/user)
+/obj/item/reagent_containers/hypospray/medipen/survival/pre_inject(mob/living/affected_mob, mob/user)
if(lavaland_equipment_pressure_check(get_turf(user)))
amount_per_transfer_from_this = initial(amount_per_transfer_from_this)
return ..()
if(DOING_INTERACTION(user, DOAFTER_SOURCE_SURVIVALPEN))
to_chat(user,span_notice("You are too busy to use \the [src]!"))
- return
+ return FALSE
to_chat(user,span_notice("You start manually releasing the low-pressure gauge..."))
if(!do_after(user, 10 SECONDS, affected_mob, interaction_key = DOAFTER_SOURCE_SURVIVALPEN))
- return
+ return FALSE
amount_per_transfer_from_this = initial(amount_per_transfer_from_this) * 0.5
return ..()
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index da3d67db744a..c3c583223adf 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -323,7 +323,7 @@
list_reagents = list(/datum/reagent/drug/bath_salts = 15)
/obj/item/reagent_containers/syringe/contraband/fentanyl
- list_reagents = list(/datum/reagent/toxin/fentanyl = 15)
+ list_reagents = list(/datum/reagent/medicine/painkiller/fentanyl = 15)
/obj/item/reagent_containers/syringe/contraband/morphine
list_reagents = list(/datum/reagent/medicine/painkiller/morphine = 15) // NON-MODULE CHANGE
diff --git a/code/modules/reagents/withdrawal/generic_addictions.dm b/code/modules/reagents/withdrawal/generic_addictions.dm
index 53cf515db462..65d9e75d42b4 100644
--- a/code/modules/reagents/withdrawal/generic_addictions.dm
+++ b/code/modules/reagents/withdrawal/generic_addictions.dm
@@ -1,26 +1,86 @@
///Opioids
/datum/addiction/opioids
- name = "opioid"
- withdrawal_stage_messages = list("I feel aches in my bodies..", "I need some pain relief...", "It aches all over...I need some opioids!")
+ name = "painkiller"
+ withdrawal_stage_messages = list(
+ "My body aches all over...",
+ "I need some pain relief...",
+ "It hurts all over...I need some painkillers!",
+ )
+ /// Multipliers to apply to pain on each withdrawal stage, compounded multiplicatively
+ var/list/pain_multipliers = list(
+ 1.25, // Stage 1
+ 1.60, // Stage 2
+ 2.50, // Stage 3
+ )
+ /// Lazylist of refs to all modified bodyparts to prevent double-dipping
+ var/list/modified_bodyparts
+ /// Minimum pain to add to bodyparts during withdrawal
+ var/min_pain = 12.5
-/datum/addiction/opioids/withdrawal_stage_1_process(mob/living/carbon/affected_carbon, seconds_per_tick)
+/datum/addiction/opioids/process_addiction(mob/living/carbon/affected_carbon, seconds_per_tick)
. = ..()
- if(SPT_PROB(10, seconds_per_tick))
- affected_carbon.emote("yawn")
+ // constantly resets pain loss cooldown
+ COOLDOWN_MINIMUM(affected_carbon.pain_controller, time_since_last_pain_loss, seconds_per_tick * 1.5 SECONDS)
+
+/datum/addiction/opioids/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon, seconds_per_tick)
+ . = ..()
+ RegisterSignal(affected_carbon, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(modify_bodypart))
+ RegisterSignal(affected_carbon, COMSIG_CARBON_POST_REMOVE_LIMB, PROC_REF(unmodify_bodypart))
+ for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
+ modify_bodypart(affected_carbon, to_hurt)
+ affected_carbon.pain_controller.refresh_pain_attributes()
/datum/addiction/opioids/withdrawal_enters_stage_2(mob/living/carbon/affected_carbon)
. = ..()
- affected_carbon.apply_status_effect(/datum/status_effect/high_blood_pressure)
+ for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
+ to_hurt.bodypart_pain_modifier *= pain_multipliers[2]
+ ADD_TRAIT(affected_carbon, TRAIT_VASOCONSTRICTED, "low_[type]")
-/datum/addiction/opioids/withdrawal_stage_3_process(mob/living/carbon/affected_carbon, seconds_per_tick)
+/datum/addiction/opioids/withdrawal_enters_stage_3(mob/living/carbon/affected_carbon)
. = ..()
- if(affected_carbon.disgust < DISGUST_LEVEL_DISGUSTED && SPT_PROB(7.5, seconds_per_tick))
- affected_carbon.adjust_disgust(12.5 * seconds_per_tick)
+ for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
+ to_hurt.bodypart_pain_modifier *= pain_multipliers[3]
+ ADD_TRAIT(affected_carbon, TRAIT_VASOCONSTRICTED, "high_[type]")
/datum/addiction/opioids/end_withdrawal(mob/living/carbon/affected_carbon)
. = ..()
- affected_carbon.remove_status_effect(/datum/status_effect/high_blood_pressure)
- affected_carbon.set_disgust(affected_carbon.disgust * 0.5) //half their disgust to help
+ UnregisterSignal(affected_carbon, COMSIG_CARBON_POST_ATTACH_LIMB)
+ UnregisterSignal(affected_carbon, COMSIG_CARBON_POST_REMOVE_LIMB)
+ for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
+ unmodify_bodypart(affected_carbon, to_hurt)
+ affected_carbon.pain_controller.refresh_pain_attributes()
+ REMOVE_TRAIT(affected_carbon, TRAIT_VASOCONSTRICTED, "low_[type]")
+ REMOVE_TRAIT(affected_carbon, TRAIT_VASOCONSTRICTED, "high_[type]")
+
+/datum/addiction/opioids/proc/modify_bodypart(mob/living/carbon/affected_carbon, obj/item/bodypart/new_limb)
+ SIGNAL_HANDLER
+
+ if(REF(new_limb) in modified_bodyparts)
+ return // already modified, don't double-dip
+
+ LAZYADD(modified_bodyparts, REF(new_limb))
+ // adds a very low base pain, so they always feel something
+ new_limb.min_pain += min_pain
+ new_limb.pain = max(new_limb.pain, new_limb.min_pain)
+
+ // then make the bodypart feel more hurt than it actually is
+ // so they can get more severe feedback effects without being in a dangerous threshold
+ for(var/i in 1 to get_withdrawal_stage(affected_carbon))
+ new_limb.bodypart_pain_modifier *= pain_multipliers[i]
+
+ new_limb.on_gain_pain_effects(min_pain, STAMINA)
+
+/datum/addiction/opioids/proc/unmodify_bodypart(mob/living/carbon/affected_carbon, obj/item/bodypart/removed_limb)
+ SIGNAL_HANDLER
+
+ if(!(REF(removed_limb) in modified_bodyparts))
+ return // wasn't modified, nothing to do
+
+ LAZYREMOVE(modified_bodyparts, REF(removed_limb))
+ removed_limb.min_pain -= min_pain
+
+ for(var/i in 1 to get_withdrawal_stage(affected_carbon))
+ removed_limb.bodypart_pain_modifier /= pain_multipliers[i]
///Stimulants
diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm
index 7d21d27f7370..fc0b91343f5a 100644
--- a/code/modules/surgery/organs/internal/heart/_heart.dm
+++ b/code/modules/surgery/organs/internal/heart/_heart.dm
@@ -36,16 +36,26 @@
/// whether the heart's been operated on to fix some of its damages
var/operated = FALSE
+ /// Keeps the random variation on BPM consistent so it doesn't look weird
+ VAR_PRIVATE/random_bpm_modifier = 0
+
/obj/item/organ/heart/update_icon_state()
. = ..()
icon_state = "[base_icon_state]-[beating ? "on" : "off"]"
-/obj/item/organ/heart/Remove(mob/living/carbon/heartless, special, movement_flags)
+/obj/item/organ/heart/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags)
+ . = ..()
+ if(beating)
+ organ_owner.remove_status_effect(/datum/status_effect/heart_attack)
+ random_bpm_modifier = rand(-5, 5)
+
+/obj/item/organ/heart/on_mob_remove(mob/living/carbon/organ_owner, special)
. = ..()
if(!special)
+ organ_owner.apply_status_effect(/datum/status_effect/heart_attack)
addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS)
playing_heartbeat_sfx = BEAT_NONE
- owner?.stop_sound_channel(CHANNEL_HEARTBEAT)
+ organ_owner.stop_sound_channel(CHANNEL_HEARTBEAT)
/obj/item/organ/heart/proc/stop_if_unowned()
if(QDELETED(src))
@@ -93,6 +103,12 @@
SShealth_updates.queue_update(owner, UPDATE_MEDHUD_STATUS|UPDATE_MEDHUD_HEALTH)
return TRUE
+/obj/item/organ/heart/proc/stop_on_beat()
+ if(owner?.needs_heart() && Stop())
+ if(owner.stat <= SOFT_CRIT && !owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB))
+ owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"))
+ to_chat(owner, span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!"))
+
/obj/item/organ/heart/OnEatFrom(eater, feeder)
. = ..()
Stop()
@@ -120,71 +136,212 @@
// Handle "sudden" heart attack
if(!beating || (organ_flags & ORGAN_FAILING))
- if(owner.can_heartattack() && Stop())
- if(owner.stat <= SOFT_CRIT && !owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB))
- owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"))
- to_chat(owner, span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!"))
+ stop_on_beat()
return
- if(!owner.client?.prefs.read_preference(/datum/preference/toggle/heartbeat))
- return
+ // randomly climbs up and down to create believable variation in heart rate
+ random_bpm_modifier = clamp(random_bpm_modifier + rand(-1, 1), -10, 10)
var/heartrate = get_heart_rate()
- switch(heartrate)
- if(1 to SLOW_HEARTBEAT_THRESHOLD)
- if(playing_heartbeat_sfx != BEAT_SLOW)
- playing_heartbeat_sfx = BEAT_SLOW
- SEND_SOUND(owner, sound('sound/health/slowbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
- if(0, SLOW_HEARTBEAT_THRESHOLD to FAST_HEARTBEAT_THRESHOLD)
- if(playing_heartbeat_sfx != BEAT_NONE)
- playing_heartbeat_sfx = BEAT_NONE
- owner.stop_sound_channel(CHANNEL_HEARTBEAT)
- if(FAST_HEARTBEAT_THRESHOLD to INFINITY)
- if(playing_heartbeat_sfx != BEAT_FAST)
- playing_heartbeat_sfx = BEAT_FAST
- SEND_SOUND(owner, sound('sound/health/fastbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
+ if(heartrate <= 0 && owner.needs_heart() && Stop())
+ stop_on_beat()
+ if(heartrate >= 160)
+ apply_organ_damage((heartrate >= 200 ? 1 : 0.5) * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC)
+
+ if(owner.client?.prefs.read_preference(/datum/preference/toggle/heartbeat))
+ switch(heartrate)
+ if(1 to SLOW_HEARTBEAT_THRESHOLD)
+ if(playing_heartbeat_sfx != BEAT_SLOW)
+ playing_heartbeat_sfx = BEAT_SLOW
+ SEND_SOUND(owner, sound('sound/health/slowbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
+ if(0, SLOW_HEARTBEAT_THRESHOLD to FAST_HEARTBEAT_THRESHOLD)
+ if(playing_heartbeat_sfx != BEAT_NONE)
+ playing_heartbeat_sfx = BEAT_NONE
+ owner.stop_sound_channel(CHANNEL_HEARTBEAT)
+ if(FAST_HEARTBEAT_THRESHOLD to INFINITY)
+ if(playing_heartbeat_sfx != BEAT_FAST)
+ playing_heartbeat_sfx = BEAT_FAST
+ SEND_SOUND(owner, sound('sound/health/fastbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
+
+ var/bloodpressure = get_blood_pressure()
+ if(bloodpressure > 140)
+ if(SPT_PROB(10, seconds_per_tick))
+ owner.adjust_dizzy_up_to(5 SECONDS, 60 SECONDS)
+ if(prob(10))
+ owner.adjust_confusion_up_to(4 SECONDS, 20 SECONDS)
+ else if(!HAS_TRAIT(owner, TRAIT_INCAPACITATED))
+ to_chat(owner, span_warning("You feel [pick("tired", "confused", "numb", "weak", "flush")]."))
+ if(SPT_PROB(10, seconds_per_tick))
+ owner.adjust_eye_blur_up_to(5 SECONDS, 60 SECONDS)
+ if(SPT_PROB(1, seconds_per_tick))
+ if(prob(90) && owner.get_bodypart(BODY_ZONE_HEAD))
+ var/face_covered = owner.obscured_slots & HIDEMASK
+ owner.bleed(rand(2, 5), leave_pool = !face_covered)
+ to_chat(owner, span_warning("You get a nosebleed."))
+ if(!face_covered)
+ owner.visible_message(span_warning("[owner] gets a nosebleed."), vision_distance = COMBAT_MESSAGE_RANGE, ignored_mobs = owner)
+ else
+ to_chat(owner, span_warning("You feel a bit nauseous."))
+ if(SPT_PROB(2, seconds_per_tick))
+ to_chat(owner, span_warning("Your chest feels [pick("tight", "uncomfortable")]."))
+ ADD_TRAIT(owner, TRAIT_LABOURED_BREATHING, type) // shortness of breath
+ else if(bloodpressure < 60)
+ if(SPT_PROB(10, seconds_per_tick))
+ owner.adjust_dizzy_up_to(5 SECONDS, 60 SECONDS)
+ if(prob(10))
+ owner.adjust_confusion_up_to(4 SECONDS, 20 SECONDS)
+ else if(!HAS_TRAIT(owner, TRAIT_INCAPACITATED))
+ to_chat(owner, span_warning("You feel [pick("lightheaded", "tired", "confused", "like you can't focus")]."))
+ if(SPT_PROB(10, seconds_per_tick))
+ owner.adjust_eye_blur_up_to(5 SECONDS, 60 SECONDS)
+ if(SPT_PROB(1, seconds_per_tick))
+ owner.adjust_disgust(10, DISGUST_LEVEL_VERYGROSS)
+ if(SPT_PROB(2, seconds_per_tick))
+ if(owner.usable_hands > 0)
+ to_chat(owner, span_warning("Your hand[owner.usable_hands > 1 ? "s" : ""] feel[owner.usable_hands > 1 ? "" : "s"] clammy."))
+ else if(!HAS_TRAIT(owner, TRAIT_KNOCKEDOUT))
+ to_chat(owner, span_warning("You feel clammy."))
+ if(SPT_PROB(1, seconds_per_tick))
+ if(prob(10))
+ owner.emote("faint")
+ else if(!HAS_TRAIT(owner, TRAIT_KNOCKEDOUT))
+ to_chat(owner, span_warning("Your feel like you're about to faint."))
+ ADD_TRAIT(owner, TRAIT_LABOURED_BREATHING, type) // shallow breathing
+ else
+ REMOVE_TRAIT(owner, TRAIT_LABOURED_BREATHING, type)
/obj/item/organ/heart/get_availability(datum/species/owner_species, mob/living/owner_mob)
return owner_species.mutantheart
-/// Gets the mob's heart rate as bpm (0 to 200(+))
-/mob/living/proc/get_bpm()
- var/heart_rate = get_heart_rate()
- if(heart_rate <= 0)
+/// Gets the heart rate of the heart (resting 80, varies between 0 and 200+)
+/obj/item/organ/heart/proc/get_heart_rate()
+ if(!is_beating() || isnull(owner))
return 0
- return heart_rate * 10 + rand(-10, 15)
+ var/base_amount = 80 + random_bpm_modifier
+ // arbitrary modifiers
+ base_amount += (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_BOOST))
+ base_amount -= (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_SLOW))
+ // hypoxia
+ base_amount += owner.getOxyLoss() / 5
+ // stress (primarily pain and shock modelled here)
+ base_amount += owner.pain_controller?.get_total_pain() / 5
+ base_amount += owner.pain_controller?.traumatic_shock / 2.5
+ // low blood volume increases heart rate
+ base_amount += (BLOOD_VOLUME_NORMAL - owner.blood_volume) / 25
+ // sprinting (to represent exercise) and actual exercise
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ base_amount += (10 * ((human_owner.sprint_length_max - human_owner.sprint_length) / human_owner.sprint_length_max))
-/// Gets the mob's heart rate scaled from 0 to 20(+)
-/mob/living/proc/get_heart_rate()
- if(stat == DEAD)
+ return max(0, round(base_amount, 1))
+
+/// Returns the strength of the heart as a multiplier (0 to 1+)
+/obj/item/organ/heart/proc/get_heart_strength()
+ if(!is_beating() || isnull(owner))
return 0
- return rand(7, 9)
+ var/heart_strength = (maxHealth - damage) / maxHealth
+ // stress (boost from adrenaline)
+ heart_strength += (owner.has_status_effect(/datum/status_effect/determined) ? 0.2 : 0)
+ // low blood volume decreases heart strength
+ heart_strength -= ((BLOOD_VOLUME_NORMAL - owner.blood_volume) / (2 * BLOOD_VOLUME_NORMAL))
-/mob/living/carbon/human/get_heart_rate()
- var/obj/item/organ/heart/heart = get_organ_slot(ORGAN_SLOT_HEART)
- return heart?.get_heart_rate() || 0
+ return max(0, round(heart_strength, 0.1))
-/// Gets the heart rate of the heart, scaled from 0 to 20(+)
-/obj/item/organ/heart/proc/get_heart_rate()
+/// Returns whether vessels are vasodialated (0.5 to 1) or vasoconstricted (1 to 2)
+/obj/item/organ/heart/proc/get_heart_vessel_status()
if(!is_beating() || isnull(owner))
return 0
- var/base_amount = 8
- base_amount += COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_BOOST)
- base_amount -= COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_SLOW)
- base_amount += round(owner.getOxyLoss() / 50, 0.5)
- base_amount += round(owner.pain_controller?.get_total_pain() / 50, 0.5)
- base_amount += round(owner.pain_controller?.traumatic_shock / 25, 0.5)
- base_amount += round((BLOOD_VOLUME_NORMAL - owner.blood_volume) / 250, 0.5)
- base_amount -= round((CONSCIOUSNESS_MAX - owner.consciousness) / 33, 0.5)
- if(ishuman(owner))
- var/mob/living/carbon/human/human_owner = owner
- base_amount += round((human_owner.sprint_length_max - human_owner.sprint_length) / human_owner.sprint_length_max, 0.1)
- var/damage_multiplier = clamp(2.5 * ((maxHealth - damage) / maxHealth), 0.5, 1)
+ var/vessel_status = 1
+ // arbitrary modifiers
+ vessel_status += (0.2 * COUNT_TRAIT_SOURCES(owner, TRAIT_VASOCONSTRICTED))
+ vessel_status -= (0.2 * COUNT_TRAIT_SOURCES(owner, TRAIT_VASODILATED))
+ // stress (primarily pain and shock modelled here) causes vasoconstriction(+)
+ vessel_status += owner.pain_controller?.get_total_pain() / 500
+ vessel_status += owner.pain_controller?.traumatic_shock / 250
+ // low blood volume causes vasoconstriction(+)
+ vessel_status += ((BLOOD_VOLUME_NORMAL - owner.blood_volume) / BLOOD_VOLUME_NORMAL)
+
+ return clamp(round(vessel_status, 0.1), 0.5, 2)
+
+/// Returns the average blood pressure of the heart, from a combination of bpm + strength + vessel status.
+/obj/item/organ/heart/proc/get_blood_pressure()
+ var/heart_rate = get_heart_rate()
+ var/heart_strength = get_heart_strength()
+ var/heart_vessel_status = get_heart_vessel_status()
+
+ // TL;DR
+ //
+ // - higher heart rate = higher blood pressure
+ // - higher oxyloss = higher heart rate = higher blood pressure
+ // - higher pain/shock = higher heart rate = higher blood pressure
+ // - lower blood volume = higher heart rate = higher blood pressure
+ //
+ // - weak heart = lower blood pressure
+ // - higher damage = weaker heart = lower blood pressure
+ // - low blood volume = weaker heart = lower blood pressure
+ //
+ // - vasoconstricted vessels = higher blood pressure
+ // - vasodilated vessels = lower blood pressure
+ // - stress = vasoconstriction(+) = higher blood pressure
+ // - low blood volume = vasoconstriction(+) = higher blood pressure
+ //
+ var/blood_pressure = (heart_rate * heart_strength * heart_vessel_status)
+
+ return max(0, round(blood_pressure, 1))
+
+/// Return the mob's heart BPM
+/mob/living/proc/get_bpm()
+ if(!(mob_biotypes & MOB_ORGANIC))
+ return 0
+
+ return rand(7, 9) * 10
+
+/mob/living/carbon/human/get_bpm()
+ var/obj/item/organ/heart/heart = get_organ_by_type(/obj/item/organ/heart)
+ return heart?.get_heart_rate() || 0
+
+/// Return the mob's blood pressure
+/mob/living/proc/get_bp()
+ if(!(mob_biotypes & MOB_ORGANIC))
+ return 0
+
+ return rand(85, 95)
+
+/mob/living/carbon/human/get_bp()
+ var/obj/item/organ/heart/heart = get_organ_by_type(/obj/item/organ/heart)
+ return heart?.get_blood_pressure() || 0
+
+/// The IRL average pressure of a pulse
+#define AVERAGE_HUMAN_PULSE_PRESSURE 40
+
+/// Return the mob's pulse pressure, which is primarily only relevant in calculating blood pressure
+/mob/living/proc/get_pp()
+ if(!(mob_biotypes & MOB_ORGANIC))
+ return 0
+
+ return AVERAGE_HUMAN_PULSE_PRESSURE
+
+/mob/living/carbon/human/get_pp()
+ var/obj/item/organ/heart/heart = get_organ_by_type(/obj/item/organ/heart)
+ if(isnull(heart))
+ return needs_heart() ? 0 : AVERAGE_HUMAN_PULSE_PRESSURE
+
+ return heart.get_heart_strength() * AVERAGE_HUMAN_PULSE_PRESSURE
+
+/// Returns the mob's blood pressure range (calculated from average bp with some variance) as a list/tuple of (diastolic pressure, systolic pressure)
+/mob/living/proc/get_bp_range()
+ var/avg_blood_pressure = get_bp()
+ var/avg_pulse_pressure = get_pp()
+
+ var/diastolic = avg_blood_pressure - (avg_pulse_pressure * 0.33)
+ var/systolic = diastolic + avg_pulse_pressure
+
+ return list(floor(diastolic), floor(systolic))
- return clamp(round(base_amount * damage_multiplier, 0.5), 1, 100)
+#undef AVERAGE_HUMAN_PULSE_PRESSURE
/obj/item/organ/heart/feel_for_damage(self_aware)
if(owner.needs_heart() && (!beating || (organ_flags & ORGAN_FAILING)))
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index f96c050afe46..8def4a1c7357 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -749,11 +749,8 @@
/// Handles what happens when we breathe in something we need to live
/obj/item/organ/lungs/proc/heal_oxyloss_on_breath(mob/living/carbon/human/breather, datum/gas_mixture/breath)
- if(HAS_TRAIT(breather, TRAIT_NOBLOOD))
- breather.adjustOxyLoss(-4)
- else
- // Less blood so breaths give you less oxygen
- breather.adjustOxyLoss(-1 * min(5, BLOOD_VOLUME_NORMAL / breather.blood_volume))
+ // Less blood makes breaths give you less oxygen
+ breather.adjustOxyLoss(-4 * min(1, HAS_TRAIT(breather, TRAIT_NOBLOOD) ? 1 : (BLOOD_VOLUME_NORMAL / breather.blood_volume)))
/// Applies suffocation side-effects to a given Human, scaling based on ratio of required pressure VS "true" pressure.
/// If pressure is greater than 0, the return value will represent the amount of gas successfully breathed.
diff --git a/maplestation.dme b/maplestation.dme
index c021c4fd9077..47ec64b79e2c 100644
--- a/maplestation.dme
+++ b/maplestation.dme
@@ -6347,12 +6347,10 @@
#include "maplestation_modules\code\datums\pain\pain_helpers.dm"
#include "maplestation_modules\code\datums\pain\pain_implements.dm"
#include "maplestation_modules\code\datums\pain\scrubber_tweak.dm"
-#include "maplestation_modules\code\datums\pain\pain_causes\opioid_addiction_pain.dm"
#include "maplestation_modules\code\datums\pain\pain_reagents\painkiller_reactions.dm"
#include "maplestation_modules\code\datums\pain\pain_reagents\painkiller_related_chems.dm"
#include "maplestation_modules\code\datums\pain\pain_reagents\painkillers.dm"
#include "maplestation_modules\code\datums\pain\pain_status_effects\anesthetic.dm"
-#include "maplestation_modules\code\datums\pain\pain_status_effects\low_blood_pressure.dm"
#include "maplestation_modules\code\datums\pain\pain_status_effects\min_pain.dm"
#include "maplestation_modules\code\datums\pain\pain_status_effects\pain_limp.dm"
#include "maplestation_modules\code\datums\pain\pain_status_effects\sharp_pain.dm"
@@ -6684,7 +6682,6 @@
#include "maplestation_modules\code\modules\mob\living\carbon\human\skrell_hair.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\autopulse.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\cpr.dm"
-#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\heart_overrides.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\armwings.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\avian_tails.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\plumage.dm"
diff --git a/maplestation_modules/code/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm
index 055e35c92726..f6e54a8c0984 100644
--- a/maplestation_modules/code/datums/pain/pain.dm
+++ b/maplestation_modules/code/datums/pain/pain.dm
@@ -262,14 +262,12 @@
if(!HAS_TRAIT_FROM(parent, TRAIT_SOFT_CRIT, PAINSHOCK))
set_pain_modifier(PAINSHOCK, 1.2)
parent.add_max_consciousness_value(PAINSHOCK, 60)
- parent.apply_status_effect(/datum/status_effect/low_blood_pressure)
parent.add_traits(list(TRAIT_SOFT_CRIT, TRAIT_LABOURED_BREATHING), PAINSHOCK)
else
if(HAS_TRAIT_FROM(parent, TRAIT_SOFT_CRIT, PAINSHOCK))
unset_pain_modifier(PAINSHOCK)
parent.remove_max_consciousness_value(PAINSHOCK)
- parent.remove_status_effect(/datum/status_effect/low_blood_pressure)
parent.remove_traits(list(TRAIT_SOFT_CRIT, TRAIT_LABOURED_BREATHING), PAINSHOCK)
#ifdef HEALTH_DEBUG
@@ -486,16 +484,6 @@
if(SPT_PROB(2, seconds_per_tick))
do_pain_message(span_userdanger(pick("Stop the pain!", "It hurts!", "You need painkillers now!")))
- if((traumatic_shock >= 20 || curr_pain >= 50) && !just_cant_feel_anything)
- if(SPT_PROB(min(curr_pain / 5, 24), seconds_per_tick))
- parent.adjust_jitter_up_to(5 SECONDS * pain_modifier, 30 SECONDS)
- if(SPT_PROB(min(curr_pain / 5, 24), seconds_per_tick))
- parent.adjust_eye_blur_up_to(5 SECONDS * pain_modifier, 30 SECONDS)
- if(SPT_PROB(min(curr_pain / 10, 12), seconds_per_tick))
- parent.adjust_dizzy_up_to(5 SECONDS * pain_modifier, 30 SECONDS)
- if(SPT_PROB(min(curr_pain / 20, 6), seconds_per_tick)) // pain applies its own stutter
- parent.adjust_stutter_up_to(5 SECONDS * pain_modifier, 30 SECONDS)
-
if((traumatic_shock >= 40 || curr_pain >= 80) && parent.stat != HARD_CRIT)
if(SPT_PROB(traumatic_shock / 60, seconds_per_tick))
//parent.vomit(VOMIT_CATEGORY_KNOCKDOWN, lost_nutrition = 7.5)
@@ -559,9 +547,6 @@
else
heart_attack_counter = 0
- if(traumatic_shock >= SHOCK_CRIT_THRESHOLD || curr_pain >= PAIN_CRIT_THRESOLD)
- parent.adjust_jitter_up_to(5 SECONDS * pain_modifier, 120 SECONDS)
-
parent.paincrit_check()
// Finally, handle pain decay over time
diff --git a/maplestation_modules/code/datums/pain/pain_causes/opioid_addiction_pain.dm b/maplestation_modules/code/datums/pain/pain_causes/opioid_addiction_pain.dm
deleted file mode 100644
index 1a9f29fed280..000000000000
--- a/maplestation_modules/code/datums/pain/pain_causes/opioid_addiction_pain.dm
+++ /dev/null
@@ -1,78 +0,0 @@
-/datum/addiction/opioids
- name = "painkiller"
- withdrawal_stage_messages = list(
- "My body aches all over...",
- "I need some pain relief...",
- "It hurts all over...I need some painkillers!",
- )
- /// Multipliers to apply to pain on each withdrawal stage, compounded multiplicatively
- var/list/pain_multipliers = list(
- 1.25, // Stage 1
- 1.60, // Stage 2
- 2.50, // Stage 3
- )
- /// Lazylist of refs to all modified bodyparts to prevent double-dipping
- var/list/modified_bodyparts
- /// Minimum pain to add to bodyparts during withdrawal
- var/min_pain = 12.5
-
-/datum/addiction/opioids/process_addiction(mob/living/carbon/affected_carbon, seconds_per_tick)
- . = ..()
- // constantly resets pain loss cooldown
- COOLDOWN_MINIMUM(affected_carbon.pain_controller, time_since_last_pain_loss, seconds_per_tick * 1.5 SECONDS)
-
-/datum/addiction/opioids/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon, seconds_per_tick)
- . = ..()
- RegisterSignal(affected_carbon, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(modify_bodypart))
- RegisterSignal(affected_carbon, COMSIG_CARBON_POST_REMOVE_LIMB, PROC_REF(unmodify_bodypart))
- for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
- modify_bodypart(affected_carbon, to_hurt)
- affected_carbon.pain_controller.refresh_pain_attributes()
-
-/datum/addiction/opioids/withdrawal_enters_stage_2(mob/living/carbon/affected_carbon)
- . = ..()
- for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
- to_hurt.bodypart_pain_modifier *= pain_multipliers[2]
-
-/datum/addiction/opioids/withdrawal_enters_stage_3(mob/living/carbon/affected_carbon)
- . = ..()
- for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
- to_hurt.bodypart_pain_modifier *= pain_multipliers[3]
-
-/datum/addiction/opioids/end_withdrawal(mob/living/carbon/affected_carbon)
- . = ..()
- UnregisterSignal(affected_carbon, COMSIG_CARBON_POST_ATTACH_LIMB)
- UnregisterSignal(affected_carbon, COMSIG_CARBON_POST_REMOVE_LIMB)
- for(var/obj/item/bodypart/to_hurt as anything in affected_carbon.bodyparts)
- unmodify_bodypart(affected_carbon, to_hurt)
- affected_carbon.pain_controller.refresh_pain_attributes()
-
-/datum/addiction/opioids/proc/modify_bodypart(mob/living/carbon/affected_carbon, obj/item/bodypart/new_limb)
- SIGNAL_HANDLER
-
- if(REF(new_limb) in modified_bodyparts)
- return // already modified, don't double-dip
-
- LAZYADD(modified_bodyparts, REF(new_limb))
- // adds a very low base pain, so they always feel something
- new_limb.min_pain += min_pain
- new_limb.pain = max(new_limb.pain, new_limb.min_pain)
-
- // then make the bodypart feel more hurt than it actually is
- // so they can get more severe feedback effects without being in a dangerous threshold
- for(var/i in 1 to get_withdrawal_stage(affected_carbon))
- new_limb.bodypart_pain_modifier *= pain_multipliers[i]
-
- new_limb.on_gain_pain_effects(min_pain, STAMINA)
-
-/datum/addiction/opioids/proc/unmodify_bodypart(mob/living/carbon/affected_carbon, obj/item/bodypart/removed_limb)
- SIGNAL_HANDLER
-
- if(!(REF(removed_limb) in modified_bodyparts))
- return // wasn't modified, nothing to do
-
- LAZYREMOVE(modified_bodyparts, REF(removed_limb))
- removed_limb.min_pain -= min_pain
-
- for(var/i in 1 to get_withdrawal_stage(affected_carbon))
- removed_limb.bodypart_pain_modifier /= pain_multipliers[i]
diff --git a/maplestation_modules/code/datums/pain/pain_implements.dm b/maplestation_modules/code/datums/pain/pain_implements.dm
index 7ced35481f86..fb8a562f521b 100644
--- a/maplestation_modules/code/datums/pain/pain_implements.dm
+++ b/maplestation_modules/code/datums/pain/pain_implements.dm
@@ -179,10 +179,15 @@
/obj/item/reagent_containers/syringe/paracetamol
name = "syringe (paracetamol)"
- desc = "Contains fiteen units of Paracetamol. Used to treat general pain. Metabolizes slowly."
+ desc = "Contains fifteen units of Paracetamol. Used to treat general pain. Metabolizes slowly."
list_reagents = list(/datum/reagent/medicine/painkiller/paracetamol = 15)
-/obj/item/reagent_containers/pill/morphine/diluted
+/obj/item/reagent_containers/syringe/fentanyl
+ name = "syringe (fentanyl)"
+ desc = "Contains three injections of Fentanyl diluted with Water. Used to treat extreme pain. Rapid acting, extremely addictive."
+ list_reagents = list(/datum/reagent/medicine/painkiller/fentanyl = 9, /datum/reagent/water = 6)
+
+/obj/item/reagent_containers/pill/morphine/smaller
desc = "Used to treat major to severe pain. Causes moderate drowsiness. Mildly addictive."
icon_state = "pill11"
list_reagents = list(/datum/reagent/medicine/painkiller/morphine = 5) // Lasts ~1 minute, heals ~10 pain per bodypart (~100 pain) // NON-MODULE CHANGE
@@ -202,8 +207,8 @@
/obj/item/reagent_containers/syringe/oxycodone
name = "syringe (oxycodone)"
- desc = "Contains three injections of Oxycodone. Used to treat severe to extreme pain. Rapid acting, may cause delirium. Very addictive."
- list_reagents = list(/datum/reagent/medicine/painkiller/oxycodone = 15)
+ desc = "Contains three injections of Oxycodone diluted with Water. Used to treat severe to extreme pain. Rapid acting, may cause delirium, very addictive."
+ list_reagents = list(/datum/reagent/medicine/painkiller/oxycodone = 12, /datum/reagent/water = 3)
/obj/item/reagent_containers/pill/aspirin_para_coffee
name = "aspirin/paracetamol/caffeine pill"
@@ -257,7 +262,7 @@
for(var/i in 1 to 3)
new /obj/item/reagent_containers/pill/paracetamol(src)
for(var/i in 1 to 3)
- new /obj/item/reagent_containers/pill/morphine/diluted(src)
+ new /obj/item/reagent_containers/pill/morphine/smaller(src)
for(var/i in 1 to 2)
new /obj/item/reagent_containers/pill/oxycodone(src)
diff --git a/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm b/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm
index 5eb7810e5b1b..6ae0f4040415 100644
--- a/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm
+++ b/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm
@@ -18,11 +18,13 @@
// However, drinking with painkillers is toxic.
var/highest_boozepwr = 0
for(var/datum/reagent/consumable/ethanol/alcohol in M.reagents.reagent_list)
- if(alcohol.boozepwr > highest_boozepwr)
- highest_boozepwr = alcohol.boozepwr
+ highest_boozepwr = max(highest_boozepwr, alcohol.boozepwr)
if(highest_boozepwr > 0)
- M.apply_damage(round(highest_boozepwr / 33, 0.5) * REM * seconds_per_tick, TOX)
+ if(SPT_PROB(highest_boozepwr / 25, seconds_per_tick))
+ to_chat(M, span_warning("You feel sick."))
+ M.losebreath += 1
+ M.apply_damage(round(highest_boozepwr / 40, 0.5) * REM * seconds_per_tick, TOX)
. = TRUE
// Morphine is the well known existing painkiller.
@@ -45,9 +47,11 @@
/datum/reagent/medicine/painkiller/morphine/on_mob_metabolize(mob/living/L)
. = ..()
L.add_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown)
+ ADD_TRAIT(L, TRAIT_HEART_RATE_SLOW, type)
/datum/reagent/medicine/painkiller/morphine/on_mob_end_metabolize(mob/living/L)
L.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown)
+ REMOVE_TRAIT(L, TRAIT_HEART_RATE_SLOW, type)
return ..()
/datum/reagent/medicine/painkiller/morphine/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired)
@@ -266,7 +270,7 @@
// Oxycodone. Very addictive, heals pain very fast, also a drug.
/datum/reagent/medicine/painkiller/oxycodone
name = "Oxycodone"
- description = "A drug that rapidly treats major to extreme pain. Highly addictive. Overdose can cause heart attacks."
+ description = "A painkiller that rapidly treats major to extreme pain. Highly addictive. Overdose can cause heart attacks."
reagent_state = LIQUID
color = "#ffcb86"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
@@ -286,9 +290,19 @@
if(SPT_PROB(33, seconds_per_tick))
M.adjust_dizzy_up_to(2 SECONDS * REM * seconds_per_tick, 10 SECONDS)
+ if(current_cycle > 5)
+ ADD_TRAIT(M, TRAIT_HEART_RATE_SLOW, "[type]_low")
+ if(current_cycle > 20)
+ ADD_TRAIT(M, TRAIT_HEART_RATE_SLOW, "[type]_med")
+
..()
return TRUE
+/datum/reagent/medicine/painkiller/oxycodone/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+ REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_SLOW, "[type]_low")
+ REMOVE_TRAIT(affected_mob, TRAIT_HEART_RATE_SLOW, "[type]_med")
+
/datum/reagent/medicine/painkiller/oxycodone/overdose_process(mob/living/carbon/M, seconds_per_tick, times_fired)
. = ..()
if(!ishuman(M))
diff --git a/maplestation_modules/code/datums/pain/pain_status_effects/low_blood_pressure.dm b/maplestation_modules/code/datums/pain/pain_status_effects/low_blood_pressure.dm
deleted file mode 100644
index 99bcdd0827b7..000000000000
--- a/maplestation_modules/code/datums/pain/pain_status_effects/low_blood_pressure.dm
+++ /dev/null
@@ -1,38 +0,0 @@
-/datum/status_effect/low_blood_pressure
- id = "low_blood_pressure"
- tick_interval = 2 SECONDS
- status_type = STATUS_EFFECT_UNIQUE
- alert_type = null
-
-/datum/status_effect/low_blood_pressure/on_apply()
- if(ishuman(owner))
- var/mob/living/carbon/human/human_owner = owner
- human_owner.physiology.bleed_mod *= 0.75
- RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_healthscan))
- return TRUE
-
-/datum/status_effect/low_blood_pressure/on_remove()
- if(ishuman(owner))
- var/mob/living/carbon/human/human_owner = owner
- human_owner.physiology.bleed_mod /= 0.75
- UnregisterSignal(owner, COMSIG_LIVING_HEALTHSCAN)
-
-/datum/status_effect/low_blood_pressure/tick(seconds_between_ticks)
- if(owner.stat == DEAD)
- return
- if(!HAS_TRAIT(owner, TRAIT_STASIS))
- if(SPT_PROB(20, seconds_between_ticks))
- owner.adjust_jitter_up_to(5 SECONDS, 60 SECONDS)
- if(SPT_PROB(10, seconds_between_ticks))
- owner.adjust_dizzy_up_to(5 SECONDS, 60 SECONDS)
- if(SPT_PROB(0.1, seconds_between_ticks))
- owner.emote("faint")
- if(SPT_PROB(10, seconds_between_ticks))
- owner.adjust_eye_blur_up_to(5 SECONDS, 60 SECONDS)
-
-/datum/status_effect/low_blood_pressure/proc/on_healthscan(datum/source, list/render_list, advanced, mob/user, mode, tochat)
- SIGNAL_HANDLER
-
- if(owner.has_status_effect(/datum/status_effect/high_blood_pressure))
- return
- render_list += "Hypotension detected.
"
diff --git a/maplestation_modules/code/datums/quirks/negative.dm b/maplestation_modules/code/datums/quirks/negative.dm
index 3b91f1f024df..c0569ea4b6d3 100644
--- a/maplestation_modules/code/datums/quirks/negative.dm
+++ b/maplestation_modules/code/datums/quirks/negative.dm
@@ -131,13 +131,13 @@
give_cane(/obj/item/cane/crutch/wood, side)
mail_goodies |= /obj/item/cane/crutch/wood
if(NO_CANE)
- mail_goodies |= /obj/item/reagent_containers/pill/morphine/diluted
+ mail_goodies |= /obj/item/reagent_containers/pill/morphine/smaller
switch(intensity)
if(INTENSITY_MEDIUM)
mail_goodies |= /obj/item/reagent_containers/pill/paracetamol
if(INTENSITY_HIGH)
- mail_goodies |= /obj/item/reagent_containers/pill/morphine/diluted
+ mail_goodies |= /obj/item/reagent_containers/pill/morphine/smaller
quirk_holder.apply_status_effect(/datum/status_effect/limp/permanent, side, intensity)
diff --git a/maplestation_modules/code/game/objects/structures/vital_reader.dm b/maplestation_modules/code/game/objects/structures/vital_reader.dm
index df90d24d8547..bd29de46dd32 100644
--- a/maplestation_modules/code/game/objects/structures/vital_reader.dm
+++ b/maplestation_modules/code/game/objects/structures/vital_reader.dm
@@ -282,11 +282,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/computer/vitals_reader/no_hand, 32)
resp_icon_state = "resp_flat"
else if(ishuman(patient))
var/mob/living/carbon/human/human_patient = patient
- switch(human_patient.get_heart_rate())
+ switch(human_patient.get_bpm())
if(0)
ekg_icon_state = "ekg_flat"
resp_icon_state = "resp_flat"
- if(11 to INFINITY)
+ if(FAST_HEARTBEAT_THRESHOLD to INFINITY)
ekg_icon_state = "ekg_fast"
var/hp_color = percent_to_color((patient.maxHealth - patient.health) / patient.maxHealth)
diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm
index 6d744297e45e..66179c35b77b 100644
--- a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm
+++ b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm
@@ -105,7 +105,7 @@
target.share_blood_on_touch(src, ITEM_SLOT_NECK)
if(!do_after(src, 6 SECONDS, target))
return
- var/own_bpm_penalty = get_heart_rate() > 11 ? 1.2 : 1
+ var/own_bpm_penalty = get_bpm() > FAST_HEARTBEAT_THRESHOLD ? 1.2 : 1
var/bpm_a = round(target.get_bpm() * own_bpm_penalty, 1)
if(bpm_a <= 0)
to_chat(src, span_warning("[target.name] has no pulse!"))
diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm
deleted file mode 100644
index ce4a5817f9fb..000000000000
--- a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-/obj/item/organ/heart/on_mob_insert(mob/living/carbon/organ_owner, special, movement_flags)
- . = ..()
- if(beating)
- organ_owner.remove_status_effect(/datum/status_effect/heart_attack)
-
-/obj/item/organ/heart/on_mob_remove(mob/living/carbon/organ_owner, special)
- . = ..()
- if(!special)
- organ_owner.apply_status_effect(/datum/status_effect/heart_attack)
-
-// Glands can't stop beating but they are cringe
-/obj/item/organ/heart/gland/Stop()
- return FALSE
diff --git a/tgui/packages/tgui/interfaces/Cryo.tsx b/tgui/packages/tgui/interfaces/Cryo.tsx
index 7c42f416d3fb..639ad45721bd 100644
--- a/tgui/packages/tgui/interfaces/Cryo.tsx
+++ b/tgui/packages/tgui/interfaces/Cryo.tsx
@@ -18,7 +18,7 @@ const damageTypes = [
type: 'bruteLoss',
},
{
- label: 'Respiratory',
+ label: 'Hypoxia',
type: 'oxyLoss',
},
{
diff --git a/tgui/packages/tgui/interfaces/OperatingComputer/types.ts b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts
index a44f887a4eb4..efbec658632c 100644
--- a/tgui/packages/tgui/interfaces/OperatingComputer/types.ts
+++ b/tgui/packages/tgui/interfaces/OperatingComputer/types.ts
@@ -75,7 +75,7 @@ export const damageTypes: damageType[] = [
type: 'toxLoss',
},
{
- label: 'Respiratory',
+ label: 'Hypoxia',
type: 'oxyLoss',
},
];