diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index d4c28d4b8400..76ee12d826a0 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -138,6 +138,9 @@ /// One application of the trait translates to -0.2 "vasodilation", which is a -0.2 multiplier to blood pressure #define TRAIT_VASODILATED "vasodilated" +/// Attempts to stabilize the heart, boosting it if it's too slow and slowing it if it's too fast. +#define TRAIT_HEART_RATE_STABILIZED "heart_rate_stabilized" + /// The trait that determines if someone has the robotic limb reattachment quirk. #define TRAIT_ROBOTIC_LIMBATTACHMENT "trait_robotic_limbattachment" @@ -210,6 +213,10 @@ #define SLOW_HEARTBEAT_THRESHOLD 60 /// Threshold that heart beat becomes "fast" #define FAST_HEARTBEAT_THRESHOLD 110 +/// Threshold that heart beat starts to cause heart damaage +#define DANGER_HEARTBEAT_THRESHOLD 160 +/// Threshold that heart beat's heart damage doubles and it has a chance to stop outright +#define DEADLY_HEARTBEAT_THRESHOLD 200 // Used in living mob offset list for determining pixel offsets #define PIXEL_W_OFFSET "w" diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index b0dea17fa75e..31c8e8f5b9a5 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -591,7 +591,7 @@ if(SEND_SIGNAL(H, COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP, user, src) & COMPONENT_DEFIB_STOP) do_cancel() return - var/obj/item/organ/heart = H.get_organ_slot(ORGAN_SLOT_HEART) + var/obj/item/organ/heart/heart = H.get_organ_slot(ORGAN_SLOT_HEART) if(H.stat == DEAD) H.visible_message(span_warning("[H]'s body convulses a bit.")) playsound(src, SFX_BODYFALL, 50, TRUE) @@ -653,20 +653,29 @@ user.audible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Patient's heart is missing. Operation aborted.")) playsound(src, 'sound/machines/defib_failed.ogg', 50, FALSE) - else if(H.undergoing_cardiac_arrest()) + else if(!heart.is_beating()) playsound(src, 'sound/machines/defib_zap.ogg', 50, TRUE, -1) - if(!(heart.organ_flags & ORGAN_FAILING)) - H.set_heartattack(FALSE) + if(heart.organ_flags & ORGAN_FAILING) + user.audible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed, heart damage detected.")) + else + heart.Restart() H.apply_status_effect(/datum/status_effect/recent_defib) user.audible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Patient's heart is now beating again.")) H.emote("gasp") H.Knockdown(8 SECONDS) H.set_jitter_if_lower(200 SECONDS) - heart?.apply_organ_damage(10, 95, ORGAN_ORGANIC) + heart.apply_organ_damage(10, 95, ORGAN_ORGANIC) SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK) do_success() - else - user.audible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed, heart damage detected.")) + + else if(heart.get_heart_rate() >= 160) + playsound(src, 'sound/machines/defib_zap.ogg', 50, TRUE, -1) + user.audible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Patient's heartbeat stabilized.")) + H.emote("gasp") + SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK) + do_success() + ADD_TRAIT(H, TRAIT_HEART_RATE_STABILIZED, TRAIT_GENERIC) + addtimer(TRAIT_CALLBACK_REMOVE(H, TRAIT_HEART_RATE_STABILIZED, TRAIT_GENERIC), 60 SECONDS) else user.visible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Patient is not in a valid state. Operation aborted.")) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 27170fd7d108..7dfb17a46c6e 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -64,7 +64,6 @@ adjustOxyLoss(1) if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY) add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.9) - add_consciousness_modifier(BLOOD_LOSS, -10) adjust_traumatic_shock(0.5 * seconds_per_tick) if(getOxyLoss() < 100) adjustOxyLoss(2) // Keep in mind if they're still breathing while bleeding - some of this will be recovered @@ -73,7 +72,6 @@ to_chat(src, span_warning("You feel very [word].")) if(BLOOD_VOLUME_SURVIVE to BLOOD_VOLUME_BAD) add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.6) - add_consciousness_modifier(BLOOD_LOSS, -20) adjust_traumatic_shock(1 * seconds_per_tick) if(getOxyLoss() < 150) adjustOxyLoss(3) @@ -84,7 +82,6 @@ to_chat(src, span_warning("You feel extremely [word].")) if(-INFINITY to BLOOD_VOLUME_SURVIVE) add_max_consciousness_value(BLOOD_LOSS, CONSCIOUSNESS_MAX * 0.2) - add_consciousness_modifier(BLOOD_LOSS, -50) adjust_traumatic_shock(3 * seconds_per_tick) set_eye_blur_if_lower(20 SECONDS) // Unconscious(10 SECONDS) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 6bcb2b0e877f..c2ef5a588301 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -96,7 +96,7 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift) if(most_toxic) return "[LOWER_TEXT(most_toxic.name)] poisoning" - if("heart_attack") + if(/datum/status_effect/cardiac_arrest::id) return "cardiac arrest" if("drunk") @@ -123,7 +123,7 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift) if(findtext(probable_cause, "addiction")) return "addiction" - return probable_cause + return replacetext(probable_cause, "_", " ") /mob/living/carbon/human/proc/reagents_readout() var/readout = "Blood:" diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index e829799fdf56..0fb843f077bf 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -138,7 +138,13 @@ "Heart rate is below average - While typically not life threatening, may be indicative of an underlying condition. \ Can be treated with medication such as [/datum/reagent/medicine/atropine::name].", add_tooltips)) - if(bpm >= FAST_HEARTBEAT_THRESHOLD) + if(bpm >= 160) + . += " " + . += span_warning(conditional_tooltip("(Alert: Tachycardia)", \ + "Heart rate is far above average, causing damage to the heart. Will lead to heart failure if conditions don't improve. \ + Defibrillate or treat with medication such as [/datum/reagent/medicine/psicodine::name].", add_tooltips)) + + else if(bpm >= FAST_HEARTBEAT_THRESHOLD) . += " " . += span_notice(conditional_tooltip("(Notice: Tachycardia)", \ "Heart rate is above average - While typically not life threatening, may be indicative of an underlying condition. \ @@ -151,26 +157,32 @@ /obj/item/organ/heart/on_life(seconds_per_tick, times_fired) . = ..() - // If the owner doesn't need a heart, we don't need to do anything with it. - if(!owner.needs_heart()) + // randomly climbs up and down to create believable variation in heart rate + random_bpm_modifier = clamp(random_bpm_modifier + rand(-1, 1), -10, 10) + + // not needing a heart and a non-beating heart are treated the same - don't check blood pressure, don't do heartbeat sfx, etc. + if(!is_beating() || !owner.needs_heart() ) return + var/heartrate = get_heart_rate() + var/bloodpressure = get_blood_pressure(heartrate) if(SEND_SIGNAL(owner, COMSIG_CARBON_HEARTBEAT, src, seconds_per_tick) & HEARTBEAT_HANDLED) return // Handle "sudden" cardiac arrest - if(organ_flags & ORGAN_FAILING) + if(heartrate <= 0) stop_on_beat() - 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) + // extreme heart rate causes heart damage + if(heartrate >= DANGER_HEARTBEAT_THRESHOLD || ((bloodpressure >= 180 && damage < high_threshold) || bloodpressure >= 220)) + apply_organ_damage((heartrate >= DEADLY_HEARTBEAT_THRESHOLD ? 1 : 0.5) * seconds_per_tick) + // if high heart beat persists, there is a chance to stop it outright + if(damage > 50 && SPT_PROB(0.5 * sqrt(damage - 50), seconds_per_tick) && IS_ORGANIC_ORGAN(src)) + stop_on_beat() - var/heartrate = get_heart_rate() - if(heartrate <= 0 && owner.needs_heart() && Stop()) + // no blood, nothing to pump + if(owner.blood_volume < BLOOD_VOLUME_SURVIVE) 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) @@ -187,15 +199,18 @@ 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() + else if(playing_heartbeat_sfx != BEAT_NONE) + playing_heartbeat_sfx = BEAT_NONE + owner.stop_sound_channel(CHANNEL_HEARTBEAT) + if(bloodpressure > 140) - if(SPT_PROB(10, seconds_per_tick)) + if(SPT_PROB(4, 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)) + if(SPT_PROB(4, 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)) @@ -210,13 +225,13 @@ 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)) + if(SPT_PROB(4, 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)) + if(SPT_PROB(4, 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) @@ -239,26 +254,30 @@ /// 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)) + if(!is_beating() || (organ_flags & ORGAN_FAILING) || isnull(owner)) return 0 var/base_amount = 80 + random_bpm_modifier + var/final_amount = base_amount // arbitrary modifiers - base_amount += (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_BOOST)) - base_amount -= (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_SLOW)) + final_amount += (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_BOOST)) + final_amount -= (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_SLOW)) // hypoxia - base_amount += owner.getOxyLoss() / 5 + final_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 + final_amount += owner.pain_controller?.get_total_pain() / 5 + final_amount += owner.pain_controller?.traumatic_shock / 2 // low blood volume increases heart rate - base_amount += (BLOOD_VOLUME_NORMAL - owner.blood_volume) / 25 + final_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)) + final_amount += (10 * ((human_owner.sprint_length_max - human_owner.sprint_length) / human_owner.sprint_length_max)) + + if(HAS_TRAIT(owner, TRAIT_HEART_RATE_STABILIZED)) + final_amount += ((final_amount - base_amount) * -0.5) - return max(0, round(base_amount, 1)) + return max(0, round(final_amount, 1)) /// Returns the strength of the heart as a multiplier (0 to 1+) /obj/item/organ/heart/proc/get_heart_strength() @@ -268,6 +287,7 @@ var/heart_strength = min(1, 0.1 + (maxHealth - (0.8 * damage)) / maxHealth) // stress (boost from adrenaline) heart_strength += (owner.has_status_effect(/datum/status_effect/determined) ? 0.2 : 0) + heart_strength += (owner.has_status_effect(/datum/status_effect/cpr_applied) ? 0.2 : 0) // low blood volume decreases heart strength heart_strength -= ((BLOOD_VOLUME_NORMAL - owner.blood_volume) / (2 * BLOOD_VOLUME_NORMAL)) @@ -291,11 +311,11 @@ 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() - +/obj/item/organ/heart/proc/get_blood_pressure( + heart_rate = get_heart_rate(), + heart_strength = get_heart_strength(), + heart_vessel_status = get_heart_vessel_status(), +) // TL;DR // // - higher heart rate = higher blood pressure @@ -368,13 +388,25 @@ #undef AVERAGE_HUMAN_PULSE_PRESSURE /obj/item/organ/heart/feel_for_damage(self_aware, medical_skill) - if(owner.needs_heart() && (!beating || (organ_flags & ORGAN_FAILING))) - return span_boldwarning("[(self_aware || medical_skill >= SKILL_LEVEL_APPRENTICE) ? "Your heart is not beating!" : "You don't feel your heart beating."]") - if(damage < low_threshold) + if(!owner.needs_heart()) return "" - if(damage < high_threshold) - return span_warning("[(self_aware || medical_skill >= SKILL_LEVEL_APPRENTICE) ? "Your heart hurts." : "It hurts, and your heart rate feels irregular."]") - return span_boldwarning("[(self_aware || medical_skill >= SKILL_LEVEL_APPRENTICE) ? "Your heart seriously hurts!" : "It seriously hurts, and your heart rate is all over the place."]") + + var/bpm_msg = "" + var/bpm = get_heart_rate() + if(bpm <= 0) + bpm_msg = span_boldwarning("You don't feel your heart beating!") + else if(bpm < SLOW_HEARTBEAT_THRESHOLD) + bpm_msg = span_warning("Your heartbeat feels very slow.") + else if(bpm > FAST_HEARTBEAT_THRESHOLD) + bpm_msg = span_warning("Your heartbeat feels very fast.") + + var/dmg_msg = "" + if(damage > high_threshold) + dmg_msg = span_boldwarning("[(self_aware || medical_skill >= SKILL_LEVEL_APPRENTICE) ? "Your heart seriously hurts!" : "It seriously hurts."]") // it = "your chest" + else if(damage > low_threshold) + dmg_msg = span_warning("[(self_aware || medical_skill >= SKILL_LEVEL_APPRENTICE) ? "Your heart hurts." : "It hurts."]") // it = "your chest" + + return bpm_msg + (bpm_msg ? "
" : "") + dmg_msg /obj/item/organ/heart/cursed name = "cursed heart" diff --git a/maplestation_modules/code/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm index 34a03d8107f4..ad690ca8cc4f 100644 --- a/maplestation_modules/code/datums/pain/pain.dm +++ b/maplestation_modules/code/datums/pain/pain.dm @@ -30,14 +30,10 @@ VAR_FINAL/base_pain_decay /// Amount of traumatic shock building up from higher levels of pain VAR_FINAL/traumatic_shock = 0 - /// Tracks how many successful heart attack rolls in a row - VAR_FINAL/heart_attack_counter = 0 /// Cooldown to track the last time we lost pain. COOLDOWN_DECLARE(time_since_last_pain_loss) /// Cooldown to track last time we sent a pain message. COOLDOWN_DECLARE(time_since_last_pain_message) - /// Cooldown to track last time heart attack counter went up. - COOLDOWN_DECLARE(time_since_last_heart_attack_counter) /datum/pain/New(mob/living/carbon/human/new_parent) if(!iscarbon(new_parent) || isdummy(new_parent)) @@ -522,41 +518,6 @@ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) - // This is death - if(traumatic_shock >= SHOCK_HEART_ATTACK_THRESHOLD && !parent.undergoing_cardiac_arrest()) - var/heart_attack_prob = 0 - if(parent.health <= parent.maxHealth * -1) - heart_attack_prob += abs(parent.health + parent.maxHealth) * 0.1 - if(traumatic_shock >= 180) - heart_attack_prob += (traumatic_shock * 0.1) - if(SPT_PROB(min(20, heart_attack_prob), seconds_per_tick)) - if(!COOLDOWN_FINISHED(src, time_since_last_heart_attack_counter)) - parent.losebreath += 1 - else if(!parent.can_heartattack()) - parent.losebreath += 4 - else if(heart_attack_counter >= 3) - to_chat(parent, span_userdanger("Your heart stops!")) - if(!parent.incapacitated()) - parent.visible_message(span_danger("[parent] grabs at [parent.p_their()] chest!"), ignored_mobs = parent) - parent.set_heartattack(TRUE) - heart_attack_counter = -2 - else - COOLDOWN_START(src, time_since_last_heart_attack_counter, 6 SECONDS) - parent.losebreath += 1 - parent.playsound_local(get_turf(parent), 'sound/effects/singlebeat.ogg', 40, 1, use_reverb = FALSE) - heart_attack_counter += 1 - switch(heart_attack_counter) - if(-INFINITY to 0) - pass() - if(1) - to_chat(parent, span_userdanger("Your pulse starts to feel irregular.")) - if(2) - to_chat(parent, span_userdanger("Your heart skips a beat.")) - else - to_chat(parent, span_userdanger("Your body starts shutting down!")) - else - heart_attack_counter = 0 - parent.paincrit_check() // Finally, handle pain decay over time