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