Reworks CPR, allowing it to extend defibrillation time (#21754)

* Update traditional CPR to loop automatically

* Implement most of the base stuff

* Bring over most defib changes so far

* Checks and fixes

* cleans up some leftovers, getting it ready for review

* Little bit of IPC flavor

* excludes non oxy-breathers, sorry!

* Better rescue breath handling

* Refactor revivability into a status effect

* Adds a little bit of feedback

* Removes the original variables

* Allows for CPR to activate cavity bombs

* Update code/modules/mob/living/carbon/human/human_mob.dm

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>

* snake case time

* breathid instead

* remove unused proc

---------

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
This commit is contained in:
Luc
2023-09-09 21:59:47 -05:00
committed by GitHub
co-authored by Henri215
parent e3eed1d23f
commit d392209cb5
14 changed files with 193 additions and 33 deletions
+3
View File
@@ -712,6 +712,9 @@
#define COMSIG_LIVING_MOB_BUMP "living_mob_bump"
///From mob/living/carbon/human/do_suicide()
#define COMSIG_HUMAN_SUICIDE_ACT "human_suicide_act"
/// Sent from mob/living/carbon/human/do_cpr(): (mob/living/carbon/human/H, new_seconds_of_life)
#define COMSIG_HUMAN_RECEIVE_CPR "human_receieve_cpr"
///From mob/living/carbon/human/attackedby(): (mob/living/carbon/human/attacker). Also found on species/disarm and species/harm
#define COMSIG_HUMAN_ATTACKED "human_attacked"
+1 -1
View File
@@ -402,7 +402,7 @@
// Defib stats
/// Past this much time the patient is unrecoverable (in deciseconds).
#define DEFIB_TIME_LIMIT (300 SECONDS)
#define BASE_DEFIB_TIME_LIMIT (300 SECONDS)
/// Brain damage starts setting in on the patient after some time left rotting.
#define DEFIB_TIME_LOSS (60 SECONDS)
+2
View File
@@ -173,6 +173,8 @@
#define STATUS_EFFECT_FAKE_VIRUS /datum/status_effect/fake_virus
/// This status effect lets the user see the lwap dots.
#define STATUS_EFFECT_LWAPSCOPE /datum/status_effect/lwap_scope
/// This status effect allows a mob to be revived with a defibrillator.
#define STATUS_EFFECT_REVIVABLE /datum/status_effect/limited_bonus/revivable
//////////////////////////
// Mind batter variants //
+2 -2
View File
@@ -243,7 +243,7 @@
// Run through some quick failure states after shocking.
var/time_dead = world.time - target.timeofdeath
if(time_dead > DEFIB_TIME_LIMIT)
if(!target.is_revivable())
user.visible_message("<span class='boldnotice'>[defib_ref] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation.</span>")
defib_success = FALSE
else if(target.getBruteLoss() >= 180 || target.getFireLoss() >= 180)
@@ -280,7 +280,7 @@
target.adjustBruteLoss(-heal_amount)
// Inflict some brain damage scaling with time spent dead
var/defib_time_brain_damage = min(100 * time_dead / DEFIB_TIME_LIMIT, 99) // 20 from 1 minute onward, +20 per minute up to 99
var/defib_time_brain_damage = min(100 * time_dead / BASE_DEFIB_TIME_LIMIT, 99) // 20 from 1 minute onward, +20 per minute up to 99
if(time_dead > DEFIB_TIME_LOSS && defib_time_brain_damage > target.getBrainLoss())
target.setBrainLoss(defib_time_brain_damage)
+50
View File
@@ -143,6 +143,56 @@
return pick(missed_messages)
/// A status effect that can have a certain amount of "bonus" duration added, which extends the duration every tick,
/// although there is a maximum amount of bonus time that can be active at any given time.
/datum/status_effect/limited_bonus
/// How much extra time has been added
var/bonus_time = 0
/// How much extra time to apply per tick
var/bonus_time_per_tick = 1 SECONDS
/// How much maximum bonus time can be active at once
var/max_bonus_time = 1 MINUTES
/datum/status_effect/limited_bonus/tick()
. = ..()
// Sure, we could do some fancy stuff with clamping, and it'd probably be a little cleaner.
// This keeps the math simple and easier to use later
if(bonus_time > bonus_time_per_tick)
duration += bonus_time_per_tick
bonus_time -= bonus_time_per_tick
/datum/status_effect/limited_bonus/proc/extend(extra_time)
bonus_time = clamp(bonus_time + extra_time, 0, max_bonus_time)
/datum/status_effect/limited_bonus/revivable
id = "revivable"
alert_type = null
status_type = STATUS_EFFECT_UNIQUE
duration = BASE_DEFIB_TIME_LIMIT
/datum/status_effect/limited_bonus/revivable/on_apply()
. = ..()
if(!iscarbon(owner))
return FALSE
RegisterSignal(owner, COMSIG_HUMAN_RECEIVE_CPR, PROC_REF(on_cpr))
RegisterSignal(owner, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
/datum/status_effect/limited_bonus/revivable/proc/on_cpr(mob/living/carbon/human/H, new_seconds)
SIGNAL_HANDLER // COMSIG_HUMAN_RECEIVE_CPR
extend(new_seconds)
/datum/status_effect/limited_bonus/revivable/proc/on_revive()
SIGNAL_HANDLER // COMSIG_LIVING_REVIVE
qdel(src)
/datum/status_effect/limited_bonus/revivable/on_remove()
// Update HUDs once the status effect is deleted to show non-revivability
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living, med_hud_set_status))
. = ..()
/datum/status_effect/charging
id = "charging"
alert_type = null
+1 -1
View File
@@ -191,7 +191,7 @@
revivable = FALSE
else if(ismachineperson(src))
revivable = TRUE
else if(timeofdeath && (round(world.time - timeofdeath) < DEFIB_TIME_LIMIT))
else if(timeofdeath && is_revivable())
revivable = TRUE
if(revivable)
+1 -1
View File
@@ -160,7 +160,7 @@ REAGENT SCANNER
if(H.timeofdeath && (H.stat == DEAD || (HAS_TRAIT(H, TRAIT_FAKEDEATH))))
to_chat(user, "<span class='notice'>Time of Death: [station_time_timestamp("hh:mm:ss", H.timeofdeath)]</span>")
var/tdelta = round(world.time - H.timeofdeath)
if(tdelta < DEFIB_TIME_LIMIT && !DNR)
if(H.is_revivable() && !DNR)
to_chat(user, "<span class='danger'>Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!</span>")
else
to_chat(user, "<font color='red'>Subject died [DisplayTimeText(tdelta)] ago.</font>")
@@ -405,7 +405,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
create_log(MISC_LOG, "DNR Enabled")
can_reenter_corpse = FALSE
if(!QDELETED(mind.current)) // Could change while they're choosing
mind.current.med_hud_set_status()
mind.current.remove_status_effect(STATUS_EFFECT_REVIVABLE)
/mob/dead/observer/proc/dead_tele()
set category = "Ghost"
-5
View File
@@ -213,11 +213,6 @@
swap_hand()
/mob/living/carbon/proc/help_shake_act(mob/living/carbon/M)
if(stat == DEAD)
if(M != src)
M.visible_message("<span class='notice'>[M] desperately shakes [src] trying to wake [p_them()] up, but sadly there is no reaction!</span>", \
"<span class='notice'>You shake [src] trying to wake [p_them()], sadly they appear to be too far gone!</span>")
return
if(health < HEALTH_THRESHOLD_CRIT)
return
if(src == M && ishuman(src))
@@ -4,6 +4,9 @@
if(!.)
return FALSE
if(!gibbed)
apply_status_effect(STATUS_EFFECT_REVIVABLE)
if(reagents)
reagents.death_metabolize(src)
+122 -19
View File
@@ -1681,49 +1681,152 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
return
..()
#define CPR_CHEST_COMPRESSION_ONLY 0.75
#define CPR_RESCUE_BREATHS 1
#define CPR_CHEST_COMPRESSION_RESTORATION (2 SECONDS)
#define CPR_BREATHS_RESTORATION (4 SECONDS)
/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/human/H)
var/static/list/effective_cpr_messages = list(
"You feel like you're able to stave off the inevitable for a little longer.",
"You can still see the color in their cheeks."
)
var/static/list/ineffective_cpr_messages = list(
"You're starting to feel them stiffen under you, but you keep going.",
"Without rescue breaths, they seem to be turning a little blue, but you press on.",
)
if(H == src)
to_chat(src, "<span class='warning'>You cannot perform CPR on yourself!</span>")
return
if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH))
to_chat(src, "<span class='warning'>[H.name] is dead!</span>")
return
if(H.receiving_cpr) // To prevent spam stacking
to_chat(src, "<span class='warning'>They are already receiving CPR!</span>")
return
if(!can_use_hands() || !has_both_hands())
to_chat(src, "<span class='warning'>You need two hands available to do CPR!</span>")
return
if(l_hand || r_hand)
to_chat(src, "<span class='warning'>You can't perform effective CPR with your hands full!</span>")
return
H.receiving_cpr = TRUE
var/cpr_modifier = get_cpr_mod(H)
visible_message("<span class='danger'>[src] is trying to perform CPR on [H.name]!</span>", "<span class='danger'>You try to perform CPR on [H.name]!</span>")
if(do_mob(src, H, 4 SECONDS))
if(H.health <= HEALTH_THRESHOLD_CRIT)
H.adjustOxyLoss(-15 * cpr_modifier)
H.SetLoseBreath(0)
H.AdjustParalysis(-2 SECONDS)
H.updatehealth("cpr")
visible_message("<span class='danger'>[src] performs CPR on [H.name]!</span>", "<span class='notice'>You perform CPR on [H.name].</span>")
if(cpr_modifier == CPR_RESCUE_BREATHS)
to_chat(H, "<span class='notice'>You feel a breath of fresh air enter your lungs. It feels good.</span>")
if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH))
if(ismachineperson(H) && do_mob(src, H, 4 SECONDS)) // hehe
visible_message(
"<span class='warning'>[src] bangs [p_their()] head on [H]'s chassis by accident!</span>",
"<span class='danger'>You go in for a rescue breath, and bang your head on [H]'s <i>machine</i> chassis. CPR's not going to work.</span>"
)
playsound(H, 'sound/weapons/ringslam.ogg', 50, TRUE)
adjustBruteLossByPart(2, "head")
H.receiving_cpr = FALSE
add_attack_logs(src, H, "CPRed", ATKLOG_ALL)
return TRUE
else
return
if(HAS_TRAIT(H, TRAIT_FAKEDEATH) || !H.is_revivable())
to_chat(src, "<span class='warning'>[H] is already too far gone for CPR...</span>")
H.receiving_cpr = FALSE
return
visible_message("<span class='danger'>[src] is trying to perform CPR on [H]'s lifeless body!</span>", "<span class='danger'>You start trying to perform CPR on [H]'s lifeless body!</span>")
while(do_mob(src, H, 4 SECONDS) && (H.stat == DEAD) && H.is_revivable())
var/timer_restored
if(cpr_modifier == CPR_CHEST_COMPRESSION_ONLY)
visible_message("<span class='notice'>[src] gives [H] chest compressions.</span>", "<span class='notice'>You can't make rescue breaths work, so you do your best to give chest compressions.</span>")
timer_restored = CPR_CHEST_COMPRESSION_RESTORATION // without rescue breaths, it won't stave off the death timer forever
else
visible_message("<span class='notice'>[src] gives [H] chest compressions and rescue breaths.</span>", "<span class='notice'>You give [H] chest compressions and rescue breaths.</span>")
timer_restored = CPR_BREATHS_RESTORATION // this, however, should keep it indefinitely postponed assuming CPR continues
if(HAS_TRAIT(H, TRAIT_FAKEDEATH))
if(prob(25))
to_chat(H, "<span class='userdanger'>Your chest burns as you receive unnecessary CPR!</span>")
continue
// this is the amount of time that should get added onto the timer.
var/new_time = (timer_restored + H.get_cpr_timer_adjustment(cpr_modifier))
SEND_SIGNAL(H, COMSIG_HUMAN_RECEIVE_CPR, (new_time SECONDS))
if(prob(5))
if(timer_restored > 4 SECONDS)
to_chat(pick(effective_cpr_messages))
else
to_chat(pick(ineffective_cpr_messages))
cpr_try_activate_bomb(H)
if(!H.is_revivable())
to_chat(src, "<span class='notice'>You feel [H]'s body is already starting to stiffen beneath you...it's too late for CPR now.</span>")
else
visible_message("<span class='notice'>[src] stops giving [H] CPR.</span>", "<span class='notice'>You stop giving [H] CPR.</span>")
H.receiving_cpr = FALSE
to_chat(src, "<span class='danger'>You need to stay still while performing CPR!</span>")
return
visible_message("<span class='danger'>[src] is trying to perform CPR on [H.name]!</span>", "<span class='danger'>You try to perform CPR on [H.name]!</span>")
if(cpr_modifier == CPR_CHEST_COMPRESSION_ONLY)
to_chat(src, "<span class='warning'>You can't get to [H]'s mouth, so your CPR will be less effective!</span>")
while(do_mob(src, H, 4 SECONDS) && H.health <= HEALTH_THRESHOLD_CRIT)
H.adjustOxyLoss(-15 * cpr_modifier)
H.SetLoseBreath(0)
H.AdjustParalysis(-2 SECONDS)
H.updatehealth("cpr")
visible_message("<span class='danger'>[src] performs CPR on [H.name]!</span>", "<span class='notice'>You perform CPR on [H.name].</span>")
cpr_try_activate_bomb(H)
if(cpr_modifier == CPR_RESCUE_BREATHS)
to_chat(H, "<span class='notice'>You feel a breath of fresh air enter your lungs. It feels good.</span>")
add_attack_logs(src, H, "CPRed", ATKLOG_ALL)
H.receiving_cpr = FALSE
visible_message("<span class='notice'>[src] stops performing CPR on [H].</span>", "<span class='notice'>You stop performing CPR on [H].</span>")
to_chat(src, "<span class='danger'>You need to stay still while performing CPR!</span>")
/mob/living/carbon/human/proc/get_cpr_mod(mob/living/carbon/human/H)
if(is_mouth_covered() || H.is_mouth_covered())
return CPR_CHEST_COMPRESSION_ONLY
if(!H.check_has_mouth() || !check_has_mouth())
return CPR_CHEST_COMPRESSION_ONLY
if(HAS_TRAIT(src, TRAIT_NOBREATH) || HAS_TRAIT(H, TRAIT_NOBREATH))
return CPR_CHEST_COMPRESSION_ONLY
if(dna?.species.breathid != H.dna?.species.breathid) // sorry non oxy-breathers
to_chat(src, "<span class='warning'>You don't think you'd be able to give [H] very effective rescue breaths...</span>")
return CPR_CHEST_COMPRESSION_ONLY
return CPR_RESCUE_BREATHS
/// Get the amount of time that this round of CPR should add to the death timer
/mob/living/carbon/human/proc/get_cpr_timer_adjustment(cpr_mod)
var/time_since_original_death = round(world.time - timeofdeath) / 10
// this looks good on desmos I guess
// goal here is that you'll get some surplus time added regardless of method if you catch them early
var/adjustment_time = max(round(-60 * log((12 + time_since_original_death) / 240)), 0)
if(cpr_mod == CPR_CHEST_COMPRESSION_ONLY)
// you just won't get as much if you're late
adjustment_time /= 2
return adjustment_time
/mob/living/carbon/human/proc/cpr_try_activate_bomb(mob/living/carbon/human/target)
var/obj/item/organ/external/chest/org = target.get_organ("chest")
if(istype(org) && istype(org.hidden, /obj/item/grenade))
var/obj/item/grenade/G = org.hidden
if(!G.active && prob(25))
to_chat(src, "<span class='notice'>You feel something <i>click</i> under your hands.</span>")
add_attack_logs(src, target, "activated an implanted grenade [G] in [target] with CPR.", ATKLOG_MOST)
playsound(target.loc, 'sound/weapons/armbomb.ogg', 60, TRUE)
G.active = TRUE
addtimer(CALLBACK(G, TYPE_PROC_REF(/obj/item/grenade, prime)), G.det_time)
#undef CPR_CHEST_COMPRESSION_ONLY
#undef CPR_RESCUE_BREATHS
#undef CPR_CHEST_COMPRESSION_RESTORATION
#undef CPR_BREATHS_RESTORATION
/mob/living/carbon/human/canBeHandcuffed()
if(get_num_arms() >= 2)
@@ -476,7 +476,7 @@
return TRUE
if(target.on_fire)
user.pat_out(target)
else if(target.health >= HEALTH_THRESHOLD_CRIT && !HAS_TRAIT(target, TRAIT_FAKEDEATH))
else if(target.health >= HEALTH_THRESHOLD_CRIT && !HAS_TRAIT(target, TRAIT_FAKEDEATH) && target.stat != DEAD)
target.help_shake_act(user)
return TRUE
else
-2
View File
@@ -73,8 +73,6 @@
update_health_hud()
med_hud_set_health()
med_hud_set_status()
if(!gibbed && !QDELETED(src))
addtimer(CALLBACK(src, PROC_REF(med_hud_set_status)), DEFIB_TIME_LIMIT + 1)
GLOB.alive_mob_list -= src
GLOB.dead_mob_list += src
@@ -845,6 +845,12 @@ STATUS EFFECTS
singlemutcheck(src, block, MUTCHK_FORCED)
dna.UpdateSE()
// Revivability
/mob/living/proc/is_revivable()
return has_status_effect(/datum/status_effect/limited_bonus/revivable)
///////////////////////////////// FROZEN /////////////////////////////////////
/mob/living/proc/IsFrozen()