diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
index 5d4568a535c..e7551394131 100644
--- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
+++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm
@@ -78,10 +78,7 @@
name = odd_organ_name()
icon_state = FLY_INFUSED_ORGAN_ICON
AddElement(/datum/element/organ_set_bonus, /datum/status_effect/organ_set_bonus/fly)
-
-/obj/item/organ/internal/heart/fly/update_icon_state()
- SHOULD_CALL_PARENT(FALSE)
- return //don't set icon thank you
+ AddElement(/datum/element/update_icon_blocker)
/obj/item/organ/internal/lungs/fly
desc = FLY_INFUSED_ORGAN_DESC
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index c1dcf68b9df..e29388c9fd6 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -4,7 +4,6 @@
icon = 'icons/obj/antags/abductor.dmi'
icon_state = "gland"
organ_flags = ORGAN_ROBOTIC // weird?
- beating = TRUE
/// Shows name of the gland as well as a description of what it does upon examination by abductor scientists and observers.
var/abductor_hint = "baseline placebo referencer"
@@ -26,12 +25,16 @@
/obj/item/organ/internal/heart/gland/Initialize(mapload)
. = ..()
icon_state = pick(list("health", "spider", "slime", "emp", "species", "egg", "vent", "mindshock", "viral"))
+ AddElement(/datum/element/update_icon_blocker)
/obj/item/organ/internal/heart/gland/examine(mob/user)
. = ..()
if(HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_SCIENTIST_TRAINING) || isobserver(user))
. += span_notice("It is \a [abductor_hint]")
+/obj/item/organ/internal/heart/gland/Stop()
+ return FALSE
+
/obj/item/organ/internal/heart/gland/proc/ownerCheck()
if(ishuman(owner))
return TRUE
@@ -102,9 +105,6 @@
update_gland_hud()
/obj/item/organ/internal/heart/gland/on_life(seconds_per_tick, times_fired)
- if(!beating)
- // alien glands are immune to stopping.
- beating = TRUE
if(!active)
return
if(!ownerCheck())
diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm
index 1e07ddc6941..ce5ff427a21 100644
--- a/code/modules/antagonists/nightmare/nightmare_organs.dm
+++ b/code/modules/antagonists/nightmare/nightmare_organs.dm
@@ -50,8 +50,8 @@
/obj/item/organ/internal/heart/nightmare
name = "heart of darkness"
desc = "An alien organ that twists and writhes when exposed to light."
- icon = 'icons/obj/medical/organs/organs.dmi'
icon_state = "demon_heart-on"
+ base_icon_state = "demon_heart"
visual = TRUE
color = "#1C1C1C"
decay_factor = 0
@@ -60,10 +60,6 @@
/// The armblade granted to the host of this heart.
var/obj/item/light_eater/blade
-/obj/item/organ/internal/heart/nightmare/Initialize(mapload)
- AddElement(/datum/element/update_icon_blocker)
- return ..()
-
/obj/item/organ/internal/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target)
if(M != user)
return ..()
@@ -94,7 +90,7 @@
QDEL_NULL(blade)
/obj/item/organ/internal/heart/nightmare/Stop()
- return 0
+ return FALSE
/obj/item/organ/internal/heart/nightmare/on_death(seconds_per_tick, times_fired)
if(!owner)
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 3b03f129008..cb0170691ae 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -237,7 +237,7 @@
//assess heart
if(body_part == BODY_ZONE_CHEST)//if we're listening to the chest
- if(isnull(heart) || !heart.beating || carbon_patient.stat == DEAD)
+ if(isnull(heart) || !heart.is_beating() || carbon_patient.stat == DEAD)
render_list += "You don't hear a heartbeat!\n"//they're dead or their heart isn't beating
else if(heart.damage > 10 || carbon_patient.blood_volume <= BLOOD_VOLUME_OKAY)
render_list += "You hear a weak heartbeat.\n"//their heart is damaged, or they have critical blood
@@ -289,7 +289,7 @@
user.visible_message(span_notice("[user] presses their fingers against [carbon_patient]'s [body_part]."), ignored_mobs = user)
//assess pulse (heart & blood level)
- if(isnull(heart) || !heart.beating || carbon_patient.blood_volume <= BLOOD_VOLUME_OKAY || carbon_patient.stat == DEAD)
+ if(isnull(heart) || !heart.is_beating() || carbon_patient.blood_volume <= BLOOD_VOLUME_OKAY || carbon_patient.stat == DEAD)
render_list += "You can't find a pulse!\n"//they're dead, their heart isn't beating, or they have critical blood
else
if(heart.damage > 10)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 3de70b2e01d..333782f0bbe 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -21,9 +21,8 @@ GLOBAL_LIST_EMPTY(dead_players_during_shift)
if(stat == DEAD)
return
stop_sound_channel(CHANNEL_HEARTBEAT)
- var/obj/item/organ/internal/heart/H = get_organ_slot(ORGAN_SLOT_HEART)
- if(H)
- H.beat = BEAT_NONE
+ var/obj/item/organ/internal/heart/human_heart = get_organ_slot(ORGAN_SLOT_HEART)
+ human_heart?.beat = BEAT_NONE
. = ..()
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 353b8de7bd7..e9adeb2728e 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -805,12 +805,19 @@
*/
/mob/living/carbon/proc/undergoing_cardiac_arrest()
var/obj/item/organ/internal/heart/heart = get_organ_slot(ORGAN_SLOT_HEART)
- if(istype(heart) && heart.beating)
+ if(istype(heart) && heart.is_beating())
return FALSE
else if(!needs_heart())
return FALSE
return TRUE
+/**
+ * Causes the mob to either start or stop having a heart attack.
+ *
+ * status - Pass TRUE to start a heart attack, or FALSE to stop one.
+ *
+ * Returns TRUE if heart status was changed (heart attack -> no heart attack, or visa versa)
+ */
/mob/living/carbon/proc/set_heartattack(status)
if(!can_heartattack())
return FALSE
@@ -819,5 +826,7 @@
if(!istype(heart))
return FALSE
- heart.beating = !status
- return TRUE
+ if(status)
+ return heart.Stop()
+
+ return heart.Restart()
diff --git a/code/modules/surgery/organs/internal/appendix/appendix_golem.dm b/code/modules/surgery/organs/internal/appendix/appendix_golem.dm
index 03b076b1b2a..ede8dfa1e6b 100644
--- a/code/modules/surgery/organs/internal/appendix/appendix_golem.dm
+++ b/code/modules/surgery/organs/internal/appendix/appendix_golem.dm
@@ -2,7 +2,7 @@
/obj/item/organ/internal/appendix/golem
name = "internal forge"
desc = "This expanded digestive chamber allows golems to smelt minerals, provided that they are immersed in lava."
- icon_state = "ethereal_heart"
+ icon_state = "ethereal_heart-off"
color = COLOR_GOLEM_GRAY
organ_flags = ORGAN_MINERAL
/// Action which performs smelting
diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm
index fb97ca7eda0..8a5646d1814 100644
--- a/code/modules/surgery/organs/internal/heart/_heart.dm
+++ b/code/modules/surgery/organs/internal/heart/_heart.dm
@@ -10,54 +10,84 @@
healing_factor = STANDARD_ORGAN_HEALING
decay_factor = 2.5 * STANDARD_ORGAN_DECAY //designed to fail around 6 minutes after death
- low_threshold_passed = "Prickles of pain appear then die out from within your chest..."
- high_threshold_passed = "Something inside your chest hurts, and the pain isn't subsiding. You notice yourself breathing far faster than before."
- now_fixed = "Your heart begins to beat again."
- high_threshold_cleared = "The pain in your chest has died down, and your breathing becomes more relaxed."
+ low_threshold_passed = span_info("Prickles of pain appear then die out from within your chest...")
+ high_threshold_passed = span_warning("Something inside your chest hurts, and the pain isn't subsiding. You notice yourself breathing far faster than before.")
+ now_fixed = span_info("Your heart begins to beat again.")
+ high_threshold_cleared = span_info("The pain in your chest has died down, and your breathing becomes more relaxed.")
- // Heart attack code is in code/modules/mob/living/carbon/human/life.dm
- var/beating = TRUE
attack_verb_continuous = list("beats", "thumps")
attack_verb_simple = list("beat", "thump")
- var/beat = BEAT_NONE//is this mob having a heatbeat sound played? if so, which?
- var/failed = FALSE //to prevent constantly running failing code
- var/operated = FALSE //whether the heart's been operated on to fix some of its damages
+
+ // Heart attack code is in code/modules/mob/living/carbon/human/life.dm
+
+ /// Whether the heart is currently beating.
+ /// Do not set this directly. Use Restart() and Stop() instead.
+ VAR_PRIVATE/beating = TRUE
+
+ /// is this mob having a heatbeat sound played? if so, which?
+ var/beat = BEAT_NONE
+ /// whether the heart's been operated on to fix some of its damages
+ var/operated = FALSE
/obj/item/organ/internal/heart/update_icon_state()
+ . = ..()
icon_state = "[base_icon_state]-[beating ? "on" : "off"]"
- return ..()
/obj/item/organ/internal/heart/Remove(mob/living/carbon/heartless, special = 0)
. = ..()
if(!special)
- addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 120)
+ addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 12 SECONDS)
+ beat = BEAT_NONE
+ owner?.stop_sound_channel(CHANNEL_HEARTBEAT)
/obj/item/organ/internal/heart/proc/stop_if_unowned()
- if(!owner)
+ if(QDELETED(src))
+ return
+ if(IS_ROBOTIC_ORGAN(src))
+ return
+ if(isnull(owner))
Stop()
/obj/item/organ/internal/heart/attack_self(mob/user)
- ..()
+ . = ..()
+ if(.)
+ return
+
if(!beating)
- user.visible_message("[user] squeezes [src] to \
- make it beat again!",span_notice("You squeeze [src] to make it beat again!"))
+ user.visible_message(
+ span_notice("[user] squeezes [src] to make it beat again!"),
+ span_notice("You squeeze [src] to make it beat again!"),
+ )
Restart()
- addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 80)
+ addtimer(CALLBACK(src, PROC_REF(stop_if_unowned)), 8 SECONDS)
+ return TRUE
/obj/item/organ/internal/heart/proc/Stop()
+ if(!beating)
+ return FALSE
+
beating = FALSE
update_appearance()
+ beat = BEAT_NONE
+ owner?.stop_sound_channel(CHANNEL_HEARTBEAT)
return TRUE
/obj/item/organ/internal/heart/proc/Restart()
+ if(beating)
+ return FALSE
+
beating = TRUE
update_appearance()
return TRUE
/obj/item/organ/internal/heart/OnEatFrom(eater, feeder)
. = ..()
- beating = FALSE
- update_appearance()
+ Stop()
+
+/// Checks if the heart is beating.
+/// Can be overridden to add more conditions for more complex hearts.
+/obj/item/organ/internal/heart/proc/is_beating()
+ return beating
/obj/item/organ/internal/heart/on_life(seconds_per_tick, times_fired)
..()
@@ -66,34 +96,32 @@
if(!owner.needs_heart())
return
- if(owner.client && beating)
- failed = FALSE
- var/sound/slowbeat = sound('sound/health/slowbeat.ogg', repeat = TRUE)
- var/sound/fastbeat = sound('sound/health/fastbeat.ogg', repeat = TRUE)
+ // Handle "sudden" heart attack
+ if(!beating || (organ_flags & ORGAN_FAILING))
+ if(owner.can_heartattack() && Stop())
+ if(owner.stat == CONSCIOUS)
+ owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"))
+ to_chat(owner, span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!"))
+ return
+
+ // Beyond deals with sound effects, so nothing needs to be done if no client
+ if(isnull(owner.client))
+ return
- if(owner.health <= owner.crit_threshold && beat != BEAT_SLOW)
+ if(owner.stat == SOFT_CRIT)
+ if(beat != BEAT_SLOW)
beat = BEAT_SLOW
- owner.playsound_local(get_turf(owner), slowbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
to_chat(owner, span_notice("You feel your heart slow down..."))
- if(beat == BEAT_SLOW && owner.health > owner.crit_threshold)
- owner.stop_sound_channel(CHANNEL_HEARTBEAT)
- beat = BEAT_NONE
-
- if(owner.has_status_effect(/datum/status_effect/jitter))
- if(owner.health > HEALTH_THRESHOLD_FULLCRIT && (!beat || beat == BEAT_SLOW))
- owner.playsound_local(get_turf(owner), fastbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
- beat = BEAT_FAST
-
- else if(beat == BEAT_FAST)
- owner.stop_sound_channel(CHANNEL_HEARTBEAT)
- beat = BEAT_NONE
-
- if(organ_flags & ORGAN_FAILING && owner.can_heartattack() && !(HAS_TRAIT(src, TRAIT_STABLEHEART))) //heart broke, stopped beating, death imminent... unless you have veins that pump blood without a heart
- if(owner.stat == CONSCIOUS)
- owner.visible_message(span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"), \
- span_userdanger("You feel a terrible pain in your chest, as if your heart has stopped!"))
- owner.set_heartattack(TRUE)
- failed = TRUE
+ SEND_SOUND(owner, sound('sound/health/slowbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
+
+ else if(owner.stat == HARD_CRIT)
+ if(beat != BEAT_FAST && owner.has_status_effect(/datum/status_effect/jitter))
+ SEND_SOUND(owner, sound('sound/health/fastbeat.ogg', repeat = TRUE, channel = CHANNEL_HEARTBEAT, volume = 40))
+ beat = BEAT_FAST
+
+ else if(beat == BEAT_SLOW)
+ owner.stop_sound_channel(CHANNEL_HEARTBEAT)
+ beat = BEAT_NONE
/obj/item/organ/internal/heart/get_availability(datum/species/owner_species, mob/living/owner_mob)
return owner_species.mutantheart
@@ -222,4 +250,3 @@
owner.heal_overall_damage(brute = 15, burn = 15, required_bodytype = BODYTYPE_ORGANIC)
if(owner.reagents.get_reagent_amount(/datum/reagent/medicine/ephedrine) < 20)
owner.reagents.add_reagent(/datum/reagent/medicine/ephedrine, 10)
-
diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
index 8ad9301fe74..78ee55b0b28 100644
--- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
+++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
@@ -1,6 +1,7 @@
/obj/item/organ/internal/heart/ethereal
name = "crystal core"
- icon_state = "ethereal_heart" //Welp. At least it's more unique in functionaliy.
+ icon_state = "ethereal_heart-on"
+ base_icon_state = "ethereal_heart"
visual = TRUE //This is used by the ethereal species for color
desc = "A crystal-like organ that functions similarly to a heart for Ethereals. It can revive its owner."
@@ -18,6 +19,7 @@
/obj/item/organ/internal/heart/ethereal/Initialize(mapload)
. = ..()
add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY)
+ update_appearance()
/obj/item/organ/internal/heart/ethereal/Insert(mob/living/carbon/heart_owner, special = FALSE, drop_if_replaced = TRUE)
. = ..()
@@ -36,7 +38,7 @@
/obj/item/organ/internal/heart/ethereal/update_overlays()
. = ..()
- var/mutable_appearance/shine = mutable_appearance(icon, icon_state = "[icon_state]_shine")
+ var/mutable_appearance/shine = mutable_appearance(icon, icon_state = "[base_icon_state]_overlay-[beating ? "on" : "off"]")
shine.appearance_flags = RESET_COLOR //No color on this, just pure white
. += shine
@@ -193,13 +195,13 @@
add_atom_colour(ethereal_heart.ethereal_color, FIXED_COLOUR_PRIORITY)
crystal_heal_timer = addtimer(CALLBACK(src, PROC_REF(heal_ethereal)), CRYSTALIZE_HEAL_TIME, TIMER_STOPPABLE)
set_light(4, 10, ethereal_heart.ethereal_color)
- update_icon()
+ update_appearance(UPDATE_OVERLAYS)
flick("ethereal_crystal_forming", src)
addtimer(CALLBACK(src, PROC_REF(start_crystalization)), 1 SECONDS)
/obj/structure/ethereal_crystal/proc/start_crystalization()
being_built = FALSE
- update_icon()
+ update_appearance(UPDATE_OVERLAYS)
/obj/structure/ethereal_crystal/atom_destruction(damage_flag)
playsound(get_turf(ethereal_heart.owner), 'sound/effects/ethereal_revive_fail.ogg', 100)
diff --git a/icons/obj/medical/organs/organs.dmi b/icons/obj/medical/organs/organs.dmi
index 94ba46568c9..3feaf4a4ca8 100644
Binary files a/icons/obj/medical/organs/organs.dmi and b/icons/obj/medical/organs/organs.dmi differ