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