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/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index fdda2e235525..cbb1ff73487b 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -269,12 +269,14 @@ infodisplay += spacesuit healths = new /atom/movable/screen/healths(null, src) + healths.name = "heart rate" infodisplay += healths hunger = new /atom/movable/screen/hunger(null, src) infodisplay += hunger healthdoll = new /atom/movable/screen/healthdoll/human(null, src) + healthdoll.name = "body status" infodisplay += healthdoll /* stamina = new /atom/movable/screen/stamina(null, src) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index ecf008b943b5..07fde83213f3 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -646,8 +646,16 @@ /atom/movable/screen/healths name = "health" + icon = 'maplestation_modules/icons/hud/screen_gen.dmi' icon_state = "health0" screen_loc = ui_health + mouse_over_pointer = MOUSE_HAND_POINTER + +/atom/movable/screen/healths/Click(location, control, params) + . = ..() + if(ishuman(usr)) + var/mob/living/carbon/human/human_user = usr + human_user.check_pulse(human_user) /atom/movable/screen/healths/alien icon = 'icons/hud/screen_alien.dmi' diff --git a/code/datums/status_effects/debuffs/screwy_hud.dm b/code/datums/status_effects/debuffs/screwy_hud.dm index 50458664c44d..122c9670a361 100644 --- a/code/datums/status_effects/debuffs/screwy_hud.dm +++ b/code/datums/status_effects/debuffs/screwy_hud.dm @@ -39,7 +39,7 @@ if(other_screwy_hud.priority > priority) return - source.hud_used.healths.icon_state = override_icon + source.hud_used.healths.icon_state = "[override_icon][source.needs_heart() ? "" : "-alwaysflat"]" return COMPONENT_OVERRIDE_HEALTH_HUD /datum/status_effect/grouped/screwy_hud/fake_dead @@ -55,7 +55,7 @@ /datum/status_effect/grouped/screwy_hud/fake_healthy id = "fake_hud_healthy" priority = 10 // fully healthy is the opposite of death, which is absolute - override_icon = "health0" + override_icon = "health1" /datum/status_effect/grouped/screwy_hud/fake_healthy/on_apply() . = ..() 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/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index d418fae0e673..45ec6c18346e 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -736,23 +736,48 @@ if(SEND_SIGNAL(src, COMSIG_CARBON_UPDATING_HEALTH_HUD) & COMPONENT_OVERRIDE_HEALTH_HUD) return + hud_used.healths.icon_state = get_health_hud_icon() + +/mob/living/carbon/proc/get_health_hud_icon() if(stat >= SOFT_CRIT) - hud_used.healths.icon_state = "health6" - return + return "health6" switch(100 - crit_percent()) if(95 to INFINITY) - hud_used.healths.icon_state = "health0" + return "health0" if(80 to 95) - hud_used.healths.icon_state = "health1" + return "health1" if(60 to 80) - hud_used.healths.icon_state = "health2" + return "health2" if(40 to 60) - hud_used.healths.icon_state = "health3" + return "health3" if(20 to 40) - hud_used.healths.icon_state = "health4" + return "health4" else - hud_used.healths.icon_state = "health5" + return "health5" + +/mob/living/carbon/human/get_health_hud_icon() + switch(get_bpm()) + if(0) // not beating or no heart + if(!needs_heart()) + return "[..()]-alwaysflat" + + return "health6" + + if(70 to 90) // standard + return "health1" + + if(60 to 70, 90 to 120) // elevated + return "health2" + + if(50 to 60, 120 to 160) // high + return "health3" + + if(30 to 50, 160 to DANGER_HEARTBEAT_THRESHOLD) // very high + return "health4" + + if(10 to 30, DANGER_HEARTBEAT_THRESHOLD to INFINITY) // critical + return "health5" /// Upsed specifically to update the spacesuit hud element /mob/living/carbon/proc/update_spacesuit_hud_icon(cell_state = "empty") 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..24ed6e422e1a 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -38,6 +38,9 @@ /// Keeps the random variation on BPM consistent so it doesn't look weird VAR_PRIVATE/random_bpm_modifier = 0 + VAR_FINAL/last_bpm = 0 + VAR_FINAL/last_bp = 0 + /obj/item/organ/heart/update_icon_state() . = ..() icon_state = "[base_icon_state]-[beating ? "on" : "off"]" @@ -46,7 +49,8 @@ . = ..() if(beating) organ_owner.remove_status_effect(/datum/status_effect/cardiac_arrest) - random_bpm_modifier = rand(-5, 5) + last_bpm = get_heart_rate() + last_bp = get_blood_pressure() /obj/item/organ/heart/on_mob_remove(mob/living/carbon/organ_owner, special) . = ..() @@ -54,6 +58,8 @@ organ_owner.apply_status_effect(/datum/status_effect/cardiac_arrest) addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS) playing_heartbeat_sfx = BEAT_NONE + last_bpm = 0 + last_bp = 0 organ_owner.stop_sound_channel(CHANNEL_HEARTBEAT) REMOVE_TRAIT(organ_owner, TRAIT_LABOURED_BREATHING, type) @@ -86,10 +92,12 @@ beating = FALSE update_appearance() playing_heartbeat_sfx = BEAT_NONE + last_bpm = 0 + last_bp = 0 if(!isnull(owner)) owner.stop_sound_channel(CHANNEL_HEARTBEAT) owner.apply_status_effect(/datum/status_effect/cardiac_arrest) - SShealth_updates.queue_update(owner, UPDATE_MEDHUD_STATUS|UPDATE_MEDHUD_HEALTH) + SShealth_updates.queue_update(owner, UPDATE_MEDHUD_STATUS|UPDATE_MEDHUD_HEALTH|UPDATE_SELF_HEALTH) return TRUE /obj/item/organ/heart/proc/Restart() @@ -100,7 +108,7 @@ update_appearance() if(!isnull(owner)) owner.remove_status_effect(/datum/status_effect/cardiac_arrest) - SShealth_updates.queue_update(owner, UPDATE_MEDHUD_STATUS|UPDATE_MEDHUD_HEALTH) + SShealth_updates.queue_update(owner, UPDATE_MEDHUD_STATUS|UPDATE_MEDHUD_HEALTH|UPDATE_SELF_HEALTH) return TRUE /obj/item/organ/heart/proc/stop_on_beat() @@ -118,27 +126,38 @@ /obj/item/organ/heart/proc/is_beating() return beating -/obj/item/organ/heart/get_status_text(advanced, add_tooltips) - if(!beating && !(organ_flags & ORGAN_FAILING) && owner.needs_heart() && owner.stat != DEAD) - return conditional_tooltip("Cardiac Arrest", \ - "Provide CPR, apply an Autopulse, or defibrillate immediately (similar electric shocks may work in emergencies).", add_tooltips) - - return ..() - /obj/item/organ/heart/get_status_appendix(advanced, add_tooltips) var/bpm = get_heart_rate() . = "[IS_ORGANIC_ORGAN(src) ? "Heart" : "Pulse"] rate: [bpm]" + span_slightly_smaller("bpm") if(bpm <= SLOW_HEARTBEAT_THRESHOLD || bpm >= FAST_HEARTBEAT_THRESHOLD) . = span_alert(.) - if(advanced && IS_ORGANIC_ORGAN(src)) + if(bpm <= 0) + if(IS_ROBOTIC_ORGAN(src)) + . += " " + . += span_boldwarning(conditional_tooltip("(Critical: Heart Malfunction)", \ + "Heart has stopped entirely. Provide CPR, apply an Autopulse, or defibrillate immediately (similar electric shocks may work in emergencies). \ + Heart transplant surgery may be necessary to return circulation if the heart has sustained critical damage.", add_tooltips)) + else + . += " " + . += span_boldwarning(conditional_tooltip("(Critical: Cardiac Arrest)", \ + "Heart has stopped entirely. Provide CPR, apply an Autopulse, or defibrillate immediately (similar electric shocks may work in emergencies). \ + Coronary artery bypass or heart transplant surgery may be necessary to return circulation if the heart has sustained critical damage.", add_tooltips)) + + else if(advanced && IS_ORGANIC_ORGAN(src)) if(bpm <= SLOW_HEARTBEAT_THRESHOLD) . += " " . += span_notice(conditional_tooltip("(Notice: Bradycardia)", \ "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 +170,33 @@ /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()) + // 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((round(last_bpm, 10) - round(heartrate, 10)) != 0) + SShealth_updates.queue_update(owner, UPDATE_SELF_HEALTH) + last_bpm = heartrate + last_bp = bloodpressure 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 +213,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 +239,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 +268,32 @@ /// 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/base_amount = 80 + rand(-5, 5) + 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)) - // hypoxia - base_amount += owner.getOxyLoss() / 5 + final_amount += (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_BOOST)) + final_amount -= (10 * COUNT_TRAIT_SOURCES(owner, TRAIT_HEART_RATE_SLOW)) + // hypoxia (not hypoxemia, which is modelled more from blood volume) + final_amount += owner.getOxyLoss() / 5 + // fake pain / general exertion + final_amount += owner.getStaminaLoss() / 10 // 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)) - return max(0, round(base_amount, 1)) + if(HAS_TRAIT(owner, TRAIT_HEART_RATE_STABILIZED)) + final_amount += ((final_amount - base_amount) * -0.5) + + 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 +303,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 +327,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 @@ -325,7 +361,7 @@ /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 heart?.last_bpm || 0 /// Return the mob's blood pressure /mob/living/proc/get_bp() @@ -336,7 +372,7 @@ /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 + return heart?.last_bp || 0 /// The IRL average pressure of a pulse #define AVERAGE_HUMAN_PULSE_PRESSURE 40 @@ -368,13 +404,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/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi index f153846787ce..9b9f750dc116 100644 Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ 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 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 e4f1b48ddca2..270e6fde76bf 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 @@ -93,26 +93,38 @@ /// Check the pulse of a target /mob/living/carbon/human/proc/check_pulse(mob/living/carbon/human/target) - if(target.on_fire) // you have better things to worry about than a pulse + if(HAS_TRAIT(src, TRAIT_INCAPACITATED) || target.on_fire) // you have better things to worry about than a pulse + return + if(DOING_INTERACTION_WITH_TARGET(src, target)) + return + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + to_chat(src, span_warning("Your hands are occupied, you can't check [target == src ? "your" : "[target.name]'s"] pulse!")) return target.balloon_alert(src, "checking pulse...") visible_message( - span_notice("[src] checks [target.name]'s pulse..."), - span_notice("You check [target.name]'s pulse..."), + span_notice("[src] checks [target == src ? p_their() : "[target.name]'s"] pulse..."), + span_notice("You check [target == src ? "your" : "[target.name]'s"] pulse..."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) target.share_blood_on_touch(src, ITEM_SLOT_NECK) if(!do_after(src, 6 SECONDS, target)) return - 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!")) + + var/base_bpm = target.get_bpm() + if(base_bpm <= 0) + to_chat(src, span_danger("[target == src ? "You have" : "[target.name] has"] no pulse!")) return - var/bpm_b = round(target.get_bpm() * 1.2 * own_bpm_penalty, 1) - to_chat(src, span_notice("[target.name]'s pulse is around [min(bpm_a, bpm_b)] to [max(bpm_a, bpm_b)] bpm.")) + var/med_skill_bonus = 0.01 * (3 - get_highest_skill_level(list(/datum/skill/first_aid, /datum/skill/surgery))) + var/own_bpm_penalty = get_bpm() > FAST_HEARTBEAT_THRESHOLD ? 0.06 : 0 + var/b_multiplier = 1.05 + med_skill_bonus + own_bpm_penalty + pick(0.01, -0.01, 0.02, -0.02, 0.03, -0.03) + var/a_multiplier = 0.95 - med_skill_bonus - own_bpm_penalty - pick(0.01, -0.01, 0.02, -0.02, 0.03, -0.03) + + var/bpm_a = floor(base_bpm * a_multiplier) + var/bpm_b = ceil(base_bpm * b_multiplier) + + to_chat(src, span_notice("[target == src ? "Your" : "[target.name]'s"] pulse is around [min(bpm_a, bpm_b)] to [max(bpm_a, bpm_b)] bpm.")) /// Number of "beats" per CPR cycle /// This corresponds to N - 1 compressions and 1 breath @@ -124,8 +136,10 @@ /mob/living/carbon/human/proc/cpr_process(mob/living/carbon/human/target, beat = 0, panicking = FALSE) set waitfor = FALSE - if(get_active_held_item() || get_inactive_held_item() || usable_hands <= 0) - to_chat(src, span_warning("Your hands are full, you can't perform CPR!")) + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + return + if(get_active_held_item() || get_inactive_held_item() || HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + to_chat(src, span_warning("Your hands are occupied, you can't perform CPR!")) return // -- cpr begins -- diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm index b7bfaf46fd3b..d11bcdd771be 100644 --- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm @@ -515,6 +515,10 @@ if(works_on_organs || works_on_biotypes || works_on_bodytypes) return NONE // if it works on robotic parts, then it works normally + var/datum/blood_type/bloodtype = source.blood_type + if(istype(chem, bloodtype.restoration_chem) || istype(chem, bloodtype.reagent_type)) + return NONE // if it's a blood booster or restorer, it works normally + // toxins accrue as toxicity, though their standard effects are otherwise blocked if(istype(chem, /datum/reagent/toxin)) var/datum/reagent/toxin/toxin_chem = chem diff --git a/maplestation_modules/icons/hud/screen_gen.dmi b/maplestation_modules/icons/hud/screen_gen.dmi new file mode 100644 index 000000000000..ef3e70c77681 Binary files /dev/null and b/maplestation_modules/icons/hud/screen_gen.dmi differ