diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index d738e5aa1f6..93ceb912480 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -462,6 +462,10 @@ #define COMPONENT_BLOCK_DEFIB (1<<0) /// If returned, don't even show the "failed" message, defer to the signal handler to do that. #define COMPONENT_DEFIB_OVERRIDE (1<<1) + /// If returned, allow to revive through false death. + #define COMPONENT_DEFIB_FAKEDEATH_ACCEPTED (1<<2) + /// If returned, make the fake death look like a unresponsive ghost. + #define COMPONENT_DEFIB_FAKEDEATH_DENIED (1<<3) ///send from defibs on ressurection: (defibber, defib_item, ghost) #define COMSIG_LIVING_DEFIBBED "living_defibbed" ///from base of mob/living/revive() (full_heal, admin_revive) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 80abb2b6f26..51647fac277 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -870,3 +870,58 @@ so as to remain in compliance with the most up-to-date laws." if(!istype(usr)) return living_owner.do_succumb(TRUE) + +/atom/movable/screen/alert/changeling_defib_revive + name = "Your heart is being defibrillated." + desc = "Click this status to be revived from fake death." + icon_state = "template" + timeout = 10 SECONDS + var/do_we_revive = FALSE + var/image/toggle_overlay + +/atom/movable/screen/alert/changeling_defib_revive/Initialize(mapload, parent_unit) + . = ..() + var/image/defib_appearance = image(parent_unit) + defib_appearance.layer = FLOAT_LAYER + defib_appearance.plane = FLOAT_PLANE + overlays += defib_appearance + +/atom/movable/screen/alert/changeling_defib_revive/attach_owner(mob/new_owner) + . = ..() + RegisterSignal(owner, COMSIG_LIVING_DEFIBBED, PROC_REF(on_defib_revive)) + +/atom/movable/screen/alert/changeling_defib_revive/Destroy() + if(owner) + UnregisterSignal(owner, COMSIG_LIVING_DEFIBBED) + if(toggle_overlay) + overlays -= toggle_overlay + qdel(toggle_overlay) + return ..() + +/atom/movable/screen/alert/changeling_defib_revive/Click() + if(!..()) + return + do_we_revive = !do_we_revive + if(!toggle_overlay) + toggle_overlay = image('icons/mob/screen_gen.dmi', icon_state = "selector") + toggle_overlay.layer = FLOAT_LAYER + toggle_overlay.plane = FLOAT_PLANE + 2 + if(do_we_revive) + overlays |= toggle_overlay + else + overlays -= toggle_overlay + +/atom/movable/screen/alert/changeling_defib_revive/proc/on_defib_revive() + . = COMPONENT_DEFIB_FAKEDEATH_DENIED + if(do_we_revive) + var/datum/antagonist/changeling/cling = owner.mind.has_antag_datum(/datum/antagonist/changeling) + cling.remove_specific_power(/datum/action/changeling/revive) + cling.regenerating = FALSE + var/heart_name = "heart" + if(iscarbon(owner)) + var/mob/living/carbon/C = owner + var/datum/organ/heart/heart = C.get_int_organ_datum(ORGAN_DATUM_HEART) + heart_name = heart.linked_organ.name + to_chat(owner, "Electricity flows through our [heart_name]! We have been brought back to life and have stopped our regeneration.") + . = COMPONENT_DEFIB_FAKEDEATH_ACCEPTED + owner.clear_alert("cling_defib") diff --git a/code/datums/components/defibrillator.dm b/code/datums/components/defibrillator.dm index ff1020e5eca..fd51de84560 100644 --- a/code/datums/components/defibrillator.dm +++ b/code/datums/components/defibrillator.dm @@ -173,6 +173,11 @@ to_chat(ghost, "Your heart is being defibrillated. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)") window_flash(ghost.client) SEND_SOUND(ghost, sound('sound/effects/genetics.ogg')) + else if(HAS_TRAIT_FROM(target, TRAIT_FAKEDEATH, CHANGELING_TRAIT)) + to_chat(target, "Your heart is being defibrillated. Click the defibrillator status to be revived!") + window_flash(target.client) + SEND_SOUND(target, sound('sound/effects/genetics.ogg')) + target.throw_alert("cling_defib", /atom/movable/screen/alert/changeling_defib_revive, alert_args = list(parent, target)) if(!do_after(user, 3 SECONDS * speed_multiplier, target = target)) // Beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process busy = FALSE @@ -238,37 +243,39 @@ playsound(get_turf(defib_ref), 'sound/machines/defib_zap.ogg', 50, 1, -1) ghost = target.get_ghost(TRUE) // We have to double check whether the dead guy has entered their body during the above - var/defib_success = TRUE - // Run through some quick failure states after shocking. var/time_dead = world.time - target.timeofdeath + var/failure_message if(!target.is_revivable()) - user.visible_message("[defib_ref] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation." else if(target.getBruteLoss() >= 180 || target.getFireLoss() >= 180) - user.visible_message("[defib_ref] buzzes: Resuscitation failed - Severe tissue damage detected.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - Severe tissue damage detected." else if(HAS_TRAIT(target, TRAIT_HUSK)) - user.visible_message("[defib_ref] buzzes: Resuscitation failed - Subject is husked.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - Subject is husked." else if(target.blood_volume < BLOOD_VOLUME_SURVIVE) - user.visible_message("[defib_ref] buzzes: Resuscitation failed - Patient blood volume critically low.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient blood volume critically low." else if(!target.get_organ_slot("brain")) // So things like headless clings don't get outed - user.visible_message("[defib_ref] buzzes: Resuscitation failed - No brain detected within patient.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - No brain detected within patient." else if(ghost) if(!ghost.can_reenter_corpse || target.suiciding) // DNR or AntagHUD - user.visible_message("[defib_ref] buzzes: Resuscitation failed - No electrical brain activity detected.") + failure_message = "[defib_ref] buzzes: Resuscitation failed - No electrical brain activity detected." else - user.visible_message("[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed.") - defib_success = FALSE - else if((signal_result & COMPONENT_BLOCK_DEFIB) || HAS_TRAIT(target, TRAIT_FAKEDEATH) || HAS_TRAIT(target, TRAIT_BADDNA) || target.suiciding) // these are a bit more arbitrary - user.visible_message("[defib_ref] buzzes: Resuscitation failed.") - defib_success = FALSE + failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed." + else if(HAS_TRAIT(target, TRAIT_FAKEDEATH)) + if(signal_result & COMPONENT_DEFIB_FAKEDEATH_DENIED) + failure_message = "[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed." + else if(signal_result & COMPONENT_DEFIB_FAKEDEATH_ACCEPTED) + // as much as I hate that this is here, it has to come after the `Patient is not in a valid state. Operation aborted.` check. + REMOVE_TRAIT(target, TRAIT_FAKEDEATH, CHANGELING_TRAIT) + else + failure_message = "[defib_ref] buzzes: Resuscitation failed." // has a fakedeath like capulettium - if(!defib_success) + else if((signal_result & COMPONENT_BLOCK_DEFIB) || HAS_TRAIT(target, TRAIT_BADDNA) || target.suiciding) // these are a bit more arbitrary + failure_message = "[defib_ref] buzzes: Resuscitation failed." + + if(failure_message) + user.visible_message(failure_message) playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) else // Heal each basic damage type by as much as we're under -100 health @@ -313,7 +320,7 @@ target.med_hud_set_status() add_attack_logs(user, target, "Revived with [defib_ref]") SSblackbox.record_feedback("tally", "players_revived", 1, "defibrillator") - SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, defib_success) + SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, isnull(failure_message)) set_cooldown(cooldown) busy = FALSE diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index b5d08685e4e..15fba006b05 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -24,7 +24,7 @@ var/req_stat = CONSCIOUS /// If this power is active or not. Used for toggleable abilities. var/active = FALSE - /// If this power can be used while the changeling has the `TRAIT_FAKE_DEATH` trait. + /// If this power can be used while the changeling has the `TRAIT_FAKEDEATH` trait. var/bypass_fake_death = FALSE /* diff --git a/code/modules/antagonists/changeling/datum_changeling.dm b/code/modules/antagonists/changeling/datum_changeling.dm index e6f36e577ae..f8507d96cd8 100644 --- a/code/modules/antagonists/changeling/datum_changeling.dm +++ b/code/modules/antagonists/changeling/datum_changeling.dm @@ -277,6 +277,22 @@ RESTRICT_TYPE(/datum/antagonist/changeling) acquired_powers += power power.on_purchase(changeling || owner.current, src) +/** + * Removes all `power_type` abilities that the changeling has. Refunds the cost of the power from our genetic points. + * + * Arugments: + * * datum/action/changeling/power - the typepath power to remove from the changeling. + * * refund_cost - if we should refund genetic points when giving the power + */ +/datum/antagonist/changeling/proc/remove_specific_power(datum/action/changeling/power_type, refund_cost = TRUE) + for(var/datum/action/changeling/power in acquired_powers) + if(!istype(power, power_type)) + continue + if(refund_cost) + genetic_points -= power.dna_cost + acquired_powers -= power + qdel(power) + /** * Store the languages from the `new_languages` list into the `absorbed_languages` list. Teaches the changeling the new languages. * diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index d826b4712e7..2ffddd180ad 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -18,11 +18,14 @@ user.updatehealth("fakedeath sting") cling.regenerating = TRUE + cling.remove_specific_power(/datum/action/changeling/revive) addtimer(CALLBACK(src, PROC_REF(ready_to_regenerate), user), CHANGELING_FAKEDEATH_TIME) SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) return TRUE /datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user) + if(!HAS_TRAIT_FROM(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT)) + return if(!QDELETED(user) && user.mind && cling?.acquired_powers) to_chat(user, "We are ready to regenerate.") cling.give_power(new /datum/action/changeling/revive) diff --git a/code/modules/antagonists/changeling/powers/revive.dm b/code/modules/antagonists/changeling/powers/revive.dm index 20e4c793330..2ed3b19acfc 100644 --- a/code/modules/antagonists/changeling/powers/revive.dm +++ b/code/modules/antagonists/changeling/powers/revive.dm @@ -11,6 +11,10 @@ if(HAS_TRAIT(user, TRAIT_UNREVIVABLE)) to_chat(user, "Something is preventing us from regenerating, we will need to revive at another point.") return FALSE + if(!HAS_TRAIT_FROM(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT)) + cling.acquired_powers -= src + Remove(user) + return REMOVE_TRAIT(user, TRAIT_FAKEDEATH, CHANGELING_TRAIT) for(var/obj/item/grab/G in user.grabbed_by) var/mob/living/carbon/M = G.assailant