mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-27 05:57:24 +01:00
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:
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 //
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user