Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions code/__DEFINES/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
23 changes: 16 additions & 7 deletions code/game/objects/items/defib.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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."))
Expand Down
3 changes: 0 additions & 3 deletions code/modules/mob/living/blood.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions code/modules/mob/living/carbon/human/death.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:"
Expand Down
104 changes: 68 additions & 36 deletions code/modules/surgery/organs/internal/heart/_heart.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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. \
Expand All @@ -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)
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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))

Expand All @@ -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
Expand Down Expand Up @@ -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 ? "<br>" : "") + dmg_msg

/obj/item/organ/heart/cursed
name = "cursed heart"
Expand Down
39 changes: 0 additions & 39 deletions maplestation_modules/code/datums/pain/pain.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
Loading