From 6d2eaef8d5389d63986cc495ca00bb104b0d5228 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 1 Apr 2026 23:19:21 -0500 Subject: [PATCH 01/11] You can feel your heart beat when self checking --- .../surgery/organs/internal/heart/_heart.dm | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 04305a6c9d94..8d50748f96e8 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -364,13 +364,25 @@ #undef AVERAGE_HUMAN_PULSE_PRESSURE /obj/item/organ/heart/feel_for_damage(self_aware) - if(owner.needs_heart() && (!beating || (organ_flags & ORGAN_FAILING))) - return span_boldwarning("[self_aware ? "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 ? "Your heart hurts." : "It hurts, and your heart rate feels irregular."]") - return span_boldwarning("[self_aware ? "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 ? "Your heart seriously hurts!" : "It seriously hurts."]") // it = "your chest" + else if(damage > low_threshold) + dmg_msg = span_warning("[self_aware ? "Your heart hurts." : "It hurts."]") // it = "your chest" + + return bpm_msg + (bpm_msg ? "
" : "") + dmg_msg /obj/item/organ/heart/cursed name = "cursed heart" From 0065c2885d02d4b33d18acf4039c6b929a06f1fd Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 2 Apr 2026 02:23:44 -0500 Subject: [PATCH 02/11] Zap --- code/__DEFINES/living.dm | 3 ++ code/game/objects/items/defib.dm | 21 +++++++++---- .../surgery/organs/internal/heart/_heart.dm | 30 ++++++++++++------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index db18bd97f5dc..49ba601dc43a 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -130,6 +130,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" diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index b0dea17fa75e..eea9ebee0dcd 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -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/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 8d50748f96e8..46194f5da489 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -137,7 +137,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. \ @@ -163,7 +169,7 @@ random_bpm_modifier = clamp(random_bpm_modifier + rand(-1, 1), -10, 10) var/heartrate = get_heart_rate() - if(heartrate <= 0 && owner.needs_heart() && Stop()) + if(heartrate <= 0 && Stop()) stop_on_beat() if(heartrate >= 160) apply_organ_damage((heartrate >= 200 ? 1 : 0.5) * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC) @@ -239,22 +245,26 @@ 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.5 // 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() From 3efc6e1baddbe07be88ab885c922980457532257 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Thu, 2 Apr 2026 03:00:59 -0500 Subject: [PATCH 03/11] Tweaks --- code/__DEFINES/living.dm | 4 ++ .../surgery/organs/internal/heart/_heart.dm | 43 +++++++++++-------- maplestation_modules/code/datums/pain/pain.dm | 39 ----------------- 3 files changed, 28 insertions(+), 58 deletions(-) diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 49ba601dc43a..9c4a33a34f75 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -205,6 +205,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/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 46194f5da489..b5a0d8967288 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -156,23 +156,28 @@ /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(!owner.needs_heart() || !is_beating()) return - // Handle "sudden" cardiac arrest - if(!beating || (organ_flags & ORGAN_FAILING)) + var/heartrate = get_heart_rate() + // 0 heart beat (likely due to heart failure) = stop beating + 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) + 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(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 && 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) @@ -189,7 +194,7 @@ 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() + var/bloodpressure = get_blood_pressure(heartrate) if(bloodpressure > 140) if(SPT_PROB(10, seconds_per_tick)) owner.adjust_dizzy_up_to(5 SECONDS, 60 SECONDS) @@ -241,7 +246,7 @@ /// 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 @@ -253,7 +258,7 @@ final_amount += owner.getOxyLoss() / 5 // stress (primarily pain and shock modelled here) final_amount += owner.pain_controller?.get_total_pain() / 5 - final_amount += owner.pain_controller?.traumatic_shock / 2.5 + final_amount += owner.pain_controller?.traumatic_shock / 2 // low blood volume increases heart rate final_amount += (BLOOD_VOLUME_NORMAL - owner.blood_volume) / 25 // sprinting (to represent exercise) and actual exercise @@ -297,11 +302,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 diff --git a/maplestation_modules/code/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm index ff83abaaf9b2..abf8e4d431df 100644 --- a/maplestation_modules/code/datums/pain/pain.dm +++ b/maplestation_modules/code/datums/pain/pain.dm @@ -28,14 +28,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)) @@ -511,41 +507,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 From 44f83b26c0d80ef6027ddd8c24ba99ab110996a0 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 5 Apr 2026 22:07:00 -0500 Subject: [PATCH 04/11] CoD --- code/modules/mob/living/carbon/human/death.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 6bcb2b0e877f..5da3cbe0195c 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("cardiac_arrest") return "cardiac arrest" if("drunk") From b8b5a50b2a9f28abd79873fb0c12045deb1d86cd Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 5 Apr 2026 22:09:43 -0500 Subject: [PATCH 05/11] Cod2 --- code/modules/mob/living/carbon/human/death.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 5da3cbe0195c..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("cardiac_arrest") + 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:" From eac97f2a388f4848b993dda1e9545f72eec3962c Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 29 Apr 2026 02:39:18 -0500 Subject: [PATCH 06/11] Tweaks --- code/game/objects/items/defib.dm | 2 +- code/modules/mob/living/blood.dm | 3 --- .../surgery/organs/internal/heart/_heart.dm | 21 +++++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index eea9ebee0dcd..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) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 1fda39e47b4f..589c4a8e8567 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/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index b5a0d8967288..8e88331ebe79 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -160,19 +160,19 @@ 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(!owner.needs_heart() || !is_beating()) + if(!is_beating() || !owner.needs_heart() ) return var/heartrate = get_heart_rate() - // 0 heart beat (likely due to heart failure) = stop beating + var/bloodpressure = get_blood_pressure(heartrate) if(heartrate <= 0) stop_on_beat() // extreme heart rate causes heart damage - if(heartrate >= DANGER_HEARTBEAT_THRESHOLD) + 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(SPT_PROB(0.5 * sqrt(damage - 50), seconds_per_tick) && IS_ORGANIC_ORGAN(src)) + if(damage > 50 && SPT_PROB(0.5 * sqrt(damage - 50), seconds_per_tick) && IS_ORGANIC_ORGAN(src)) stop_on_beat() // no blood, nothing to pump @@ -194,15 +194,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(heartrate) + 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)) @@ -217,13 +220,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) From ca42f2825622dbc38dafcdc0fec159a25b21a210 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 29 Apr 2026 02:47:46 -0500 Subject: [PATCH 07/11] CPR --- code/modules/surgery/organs/internal/heart/_heart.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 8e88331ebe79..7dfa1c2fadcb 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -282,6 +282,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)) From 5dc482e051c3b716c97540604284c1630286217e Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 10 May 2026 18:19:20 -0500 Subject: [PATCH 08/11] Go --- code/modules/antagonists/cult/cult.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index edf923ee423c..91c23d7345a8 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -290,7 +290,7 @@ /datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visualsOnly) equipped.eye_color_left = BLOODCULT_EYE equipped.eye_color_right = BLOODCULT_EYE - equipped.update_body() + equipped.update_eyes() ///Returns whether the given mob is convertable to the blood cult /proc/is_convertable_to_cult(mob/living/target, datum/team/cult/specific_cult) From d904dd191fdca5b56b980b483a51db6c505641f8 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 10 May 2026 21:25:20 -0500 Subject: [PATCH 09/11] Revert "Go" This reverts commit 5dc482e051c3b716c97540604284c1630286217e. --- code/modules/antagonists/cult/cult.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 91c23d7345a8..edf923ee423c 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -290,7 +290,7 @@ /datum/outfit/cultist/post_equip(mob/living/carbon/human/equipped, visualsOnly) equipped.eye_color_left = BLOODCULT_EYE equipped.eye_color_right = BLOODCULT_EYE - equipped.update_eyes() + equipped.update_body() ///Returns whether the given mob is convertable to the blood cult /proc/is_convertable_to_cult(mob/living/target, datum/team/cult/specific_cult) From f200b5cbb19ac568909d0b0e31a4f8554489d7f6 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:03:27 -0500 Subject: [PATCH 10/11] Snouts push masks out a bit (#94640) --- code/__DEFINES/inventory.dm | 14 ++++++-------- code/_globalvars/bitfields.dm | 1 - code/modules/clothing/under/shorts.dm | 2 +- .../surgery/bodyparts/worn_feature_offset.dm | 16 ++++++++++++---- .../surgery/organs/external/_visual_organs.dm | 18 ++++++++++++++++++ 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index 20c003b96e0e..0e4a517124c2 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -154,16 +154,14 @@ DEFINE_BITFIELD(no_equip_flags, list( #define DIGITIGRADE_STYLE 2 //Flags (actual flags, fucker ^) for /obj/item/var/supports_variations_flags -///No alternative sprites based on bodytype -#define CLOTHING_NO_VARIATION (1<<0) -///Has a sprite for digitigrade legs specifically. -#define CLOTHING_DIGITIGRADE_VARIATION (1<<1) -///The sprite works fine for digitigrade legs as-is. -#define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<2) +/// Has a sprite for digitigrade legs specifically. +#define CLOTHING_DIGITIGRADE_VARIATION (1<<0) +/// The sprite works fine for digitigrade legs as-is. +#define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<1) /// Auto-generates the leg portion of the sprite with GAGS -#define CLOTHING_DIGITIGRADE_MASK (1<<3) +#define CLOTHING_DIGITIGRADE_MASK (1<<2) /// When worn by a mob with digitigrade, apply a filter -#define CLOTHING_DIGITIGRADE_FILTER (1<<4) +#define CLOTHING_DIGITIGRADE_FILTER (1<<3) /// All variation flags which render correctly on a digitigrade leg setup #define DIGITIGRADE_VARIATIONS (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON|CLOTHING_DIGITIGRADE_FILTER|CLOTHING_DIGITIGRADE_MASK) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index a299b719cda8..f50e38442148 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -470,7 +470,6 @@ DEFINE_BITFIELD(head_flags, list( )) DEFINE_BITFIELD(supports_variations_flags, list( - "CLOTHING_NO_VARIATION" = CLOTHING_NO_VARIATION, "CLOTHING_DIGITIGRADE_VARIATION" = CLOTHING_DIGITIGRADE_VARIATION, "CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON" = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON, "CLOTHING_DIGITIGRADE_FILTER" = CLOTHING_DIGITIGRADE_FILTER, diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index f87c38132074..b95664d2088b 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -11,7 +11,7 @@ article = "a pair of" body_parts_covered = GROIN female_sprite_flags = NO_FEMALE_UNIFORM - supports_variations_flags = CLOTHING_NO_VARIATION + supports_variations_flags = NONE can_adjust = FALSE species_exception = list(/datum/species/golem) flags_1 = IS_PLAYER_COLORABLE_1 diff --git a/code/modules/surgery/bodyparts/worn_feature_offset.dm b/code/modules/surgery/bodyparts/worn_feature_offset.dm index 7e98c626cc42..4ddd0fa87a98 100644 --- a/code/modules/surgery/bodyparts/worn_feature_offset.dm +++ b/code/modules/surgery/bodyparts/worn_feature_offset.dm @@ -19,7 +19,6 @@ list/offset_y = list("south" = 0), ) attached_part.feature_offsets[feature_key] = src - owner = attached_part.owner src.attached_part = attached_part src.feature_key = feature_key src.offset_x = offset_x @@ -28,13 +27,20 @@ if (length(offset_x) <= 1 && length(offset_y) <= 1) return // We don't need to do any extra signal handling - if (!isnull(owner)) - changed_owner(owner) + changed_owner(owner, attached_part.owner) RegisterSignal(attached_part, COMSIG_BODYPART_CHANGED_OWNER, PROC_REF(changed_owner)) +/datum/worn_feature_offset/Destroy(force) + attached_part.feature_offsets -= feature_key + attached_part = null + changed_owner(null, null) + return ..() + /// Returns the current offset which should be used for this feature /datum/worn_feature_offset/proc/get_offset() var/current_dir = owner ? owner.dir : SOUTH + if(ISDIAGONALDIR(current_dir)) + current_dir = current_dir & (EAST|WEST) current_dir = dir2text(current_dir) var/x = length(offset_x) ? ((current_dir in offset_x) ? offset_x[current_dir] : offset_x["south"]) : 0 var/y = length(offset_y) ? ((current_dir in offset_y) ? offset_y[current_dir] : offset_y["south"]) : 0 @@ -49,9 +55,11 @@ /// When the owner of the bodypart changes, update our signal registrations /datum/worn_feature_offset/proc/changed_owner(obj/item/bodypart/part, mob/living/new_owner, mob/living/old_owner) SIGNAL_HANDLER + if(isnull(old_owner)) + old_owner = owner owner = new_owner if (!isnull(old_owner)) - UnregisterSignal(old_owner, COMSIG_ATOM_POST_DIR_CHANGE) + UnregisterSignal(old_owner, list(COMSIG_ATOM_POST_DIR_CHANGE, COMSIG_QDELETING)) if (!isnull(new_owner)) RegisterSignal(new_owner, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_dir_change)) RegisterSignal(new_owner, COMSIG_QDELETING, PROC_REF(on_owner_deleted)) diff --git a/code/modules/surgery/organs/external/_visual_organs.dm b/code/modules/surgery/organs/external/_visual_organs.dm index 64e2f1f7b208..49cb1a6b5a3e 100644 --- a/code/modules/surgery/organs/external/_visual_organs.dm +++ b/code/modules/surgery/organs/external/_visual_organs.dm @@ -191,6 +191,24 @@ Unlike normal organs, we're actually inside a persons limbs at all times bodypart_overlay = /datum/bodypart_overlay/mutant/snout organ_flags = parent_type::organ_flags | ORGAN_EXTERNAL + /// Offset to apply to equipment worn on the mouth we give to the head. + var/datum/worn_feature_offset/worn_mask_offset + +/obj/item/organ/snout/on_bodypart_insert(obj/item/bodypart/head/limb) + . = ..() + if(isnull(limb.worn_mask_offset)) + worn_mask_offset = limb.worn_mask_offset = new( + attached_part = limb, + feature_key = OFFSET_FACEMASK, + offset_x = list("east" = 1, "west" = -1), + ) + +/obj/item/organ/snout/on_bodypart_remove(obj/item/bodypart/head/limb, movement_flags) + if(worn_mask_offset) + QDEL_NULL(worn_mask_offset) + limb.worn_mask_offset = null + return ..() + /datum/bodypart_overlay/mutant/snout layers = EXTERNAL_ADJACENT feature_key = "snout" From d055a491c041078538d547a225477d961eeb4a5a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 10 May 2026 22:17:10 -0500 Subject: [PATCH 11/11] Revert "Snouts push masks out a bit (#94640)" This reverts commit f200b5cbb19ac568909d0b0e31a4f8554489d7f6. --- code/__DEFINES/inventory.dm | 14 ++++++++------ code/_globalvars/bitfields.dm | 1 + code/modules/clothing/under/shorts.dm | 2 +- .../surgery/bodyparts/worn_feature_offset.dm | 16 ++++------------ .../surgery/organs/external/_visual_organs.dm | 18 ------------------ 5 files changed, 14 insertions(+), 37 deletions(-) diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index 0e4a517124c2..20c003b96e0e 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -154,14 +154,16 @@ DEFINE_BITFIELD(no_equip_flags, list( #define DIGITIGRADE_STYLE 2 //Flags (actual flags, fucker ^) for /obj/item/var/supports_variations_flags -/// Has a sprite for digitigrade legs specifically. -#define CLOTHING_DIGITIGRADE_VARIATION (1<<0) -/// The sprite works fine for digitigrade legs as-is. -#define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<1) +///No alternative sprites based on bodytype +#define CLOTHING_NO_VARIATION (1<<0) +///Has a sprite for digitigrade legs specifically. +#define CLOTHING_DIGITIGRADE_VARIATION (1<<1) +///The sprite works fine for digitigrade legs as-is. +#define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<2) /// Auto-generates the leg portion of the sprite with GAGS -#define CLOTHING_DIGITIGRADE_MASK (1<<2) +#define CLOTHING_DIGITIGRADE_MASK (1<<3) /// When worn by a mob with digitigrade, apply a filter -#define CLOTHING_DIGITIGRADE_FILTER (1<<3) +#define CLOTHING_DIGITIGRADE_FILTER (1<<4) /// All variation flags which render correctly on a digitigrade leg setup #define DIGITIGRADE_VARIATIONS (CLOTHING_DIGITIGRADE_VARIATION|CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON|CLOTHING_DIGITIGRADE_FILTER|CLOTHING_DIGITIGRADE_MASK) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index f50e38442148..a299b719cda8 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -470,6 +470,7 @@ DEFINE_BITFIELD(head_flags, list( )) DEFINE_BITFIELD(supports_variations_flags, list( + "CLOTHING_NO_VARIATION" = CLOTHING_NO_VARIATION, "CLOTHING_DIGITIGRADE_VARIATION" = CLOTHING_DIGITIGRADE_VARIATION, "CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON" = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON, "CLOTHING_DIGITIGRADE_FILTER" = CLOTHING_DIGITIGRADE_FILTER, diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index b95664d2088b..f87c38132074 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -11,7 +11,7 @@ article = "a pair of" body_parts_covered = GROIN female_sprite_flags = NO_FEMALE_UNIFORM - supports_variations_flags = NONE + supports_variations_flags = CLOTHING_NO_VARIATION can_adjust = FALSE species_exception = list(/datum/species/golem) flags_1 = IS_PLAYER_COLORABLE_1 diff --git a/code/modules/surgery/bodyparts/worn_feature_offset.dm b/code/modules/surgery/bodyparts/worn_feature_offset.dm index 4ddd0fa87a98..7e98c626cc42 100644 --- a/code/modules/surgery/bodyparts/worn_feature_offset.dm +++ b/code/modules/surgery/bodyparts/worn_feature_offset.dm @@ -19,6 +19,7 @@ list/offset_y = list("south" = 0), ) attached_part.feature_offsets[feature_key] = src + owner = attached_part.owner src.attached_part = attached_part src.feature_key = feature_key src.offset_x = offset_x @@ -27,20 +28,13 @@ if (length(offset_x) <= 1 && length(offset_y) <= 1) return // We don't need to do any extra signal handling - changed_owner(owner, attached_part.owner) + if (!isnull(owner)) + changed_owner(owner) RegisterSignal(attached_part, COMSIG_BODYPART_CHANGED_OWNER, PROC_REF(changed_owner)) -/datum/worn_feature_offset/Destroy(force) - attached_part.feature_offsets -= feature_key - attached_part = null - changed_owner(null, null) - return ..() - /// Returns the current offset which should be used for this feature /datum/worn_feature_offset/proc/get_offset() var/current_dir = owner ? owner.dir : SOUTH - if(ISDIAGONALDIR(current_dir)) - current_dir = current_dir & (EAST|WEST) current_dir = dir2text(current_dir) var/x = length(offset_x) ? ((current_dir in offset_x) ? offset_x[current_dir] : offset_x["south"]) : 0 var/y = length(offset_y) ? ((current_dir in offset_y) ? offset_y[current_dir] : offset_y["south"]) : 0 @@ -55,11 +49,9 @@ /// When the owner of the bodypart changes, update our signal registrations /datum/worn_feature_offset/proc/changed_owner(obj/item/bodypart/part, mob/living/new_owner, mob/living/old_owner) SIGNAL_HANDLER - if(isnull(old_owner)) - old_owner = owner owner = new_owner if (!isnull(old_owner)) - UnregisterSignal(old_owner, list(COMSIG_ATOM_POST_DIR_CHANGE, COMSIG_QDELETING)) + UnregisterSignal(old_owner, COMSIG_ATOM_POST_DIR_CHANGE) if (!isnull(new_owner)) RegisterSignal(new_owner, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_dir_change)) RegisterSignal(new_owner, COMSIG_QDELETING, PROC_REF(on_owner_deleted)) diff --git a/code/modules/surgery/organs/external/_visual_organs.dm b/code/modules/surgery/organs/external/_visual_organs.dm index 49cb1a6b5a3e..64e2f1f7b208 100644 --- a/code/modules/surgery/organs/external/_visual_organs.dm +++ b/code/modules/surgery/organs/external/_visual_organs.dm @@ -191,24 +191,6 @@ Unlike normal organs, we're actually inside a persons limbs at all times bodypart_overlay = /datum/bodypart_overlay/mutant/snout organ_flags = parent_type::organ_flags | ORGAN_EXTERNAL - /// Offset to apply to equipment worn on the mouth we give to the head. - var/datum/worn_feature_offset/worn_mask_offset - -/obj/item/organ/snout/on_bodypart_insert(obj/item/bodypart/head/limb) - . = ..() - if(isnull(limb.worn_mask_offset)) - worn_mask_offset = limb.worn_mask_offset = new( - attached_part = limb, - feature_key = OFFSET_FACEMASK, - offset_x = list("east" = 1, "west" = -1), - ) - -/obj/item/organ/snout/on_bodypart_remove(obj/item/bodypart/head/limb, movement_flags) - if(worn_mask_offset) - QDEL_NULL(worn_mask_offset) - limb.worn_mask_offset = null - return ..() - /datum/bodypart_overlay/mutant/snout layers = EXTERNAL_ADJACENT feature_key = "snout"