Skip to content

Commit

Permalink
Reworks CPR (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMelbert authored Nov 8, 2023
1 parent 6d3fe96 commit 602823b
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 11 deletions.
3 changes: 1 addition & 2 deletions code/modules/mob/living/carbon/human/life.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
HM.on_life(seconds_per_tick, times_fired)

if(stat != DEAD)
//heart attack stuff
handle_heart(seconds_per_tick, times_fired)
// handle_heart(seconds_per_tick, times_fired) // NON-MODULE CHANGE
handle_liver(seconds_per_tick, times_fired)

dna.species.spec_life(src, seconds_per_tick, times_fired) // for mutantraces
Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/human/species.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(attacker_style?.help_act(user, target) == MARTIAL_ATTACK_SUCCESS)
return TRUE

if(target.body_position == STANDING_UP || target.appears_alive())
if(!target.undergoing_cardiac_arrest() && (target.body_position == STANDING_UP || target.appears_alive())) // NON-MODULE CHANGE
target.help_shake_act(user)
if(target != user)
log_combat(user, target, "shaken")
Expand Down
7 changes: 6 additions & 1 deletion code/modules/mob/living/carbon/life.dm
Original file line number Diff line number Diff line change
Expand Up @@ -778,4 +778,9 @@
if(!istype(heart))
return

heart.beating = !status
// NON-MODULE CHANGE START
if(status)
heart.Stop()
else
heart.Restart()
// NON-MODULE CHANGE END
2 changes: 1 addition & 1 deletion code/modules/mob/living/status_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,6 @@

/// Helper to check if we seem to be alive or not
/mob/living/proc/appears_alive()
return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH)
return stat != DEAD && !HAS_TRAIT(src, TRAIT_FAKEDEATH)

#undef IS_STUN_IMMUNE
3 changes: 1 addition & 2 deletions code/modules/surgery/organs/heart.dm
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@

/obj/item/organ/internal/heart/OnEatFrom(eater, feeder)
. = ..()
beating = FALSE
update_appearance()
Stop() // NON-MODULE CHANGE

/obj/item/organ/internal/heart/on_life(seconds_per_tick, times_fired)
..()
Expand Down
4 changes: 4 additions & 0 deletions maplestation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -5572,6 +5572,7 @@
#include "maplestation_modules\code\modules\language\japanese.dm"
#include "maplestation_modules\code\modules\language\language_holder.dm"
#include "maplestation_modules\code\modules\language\skrellian.dm"
#include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\medbay.dm"
#include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\mining.dm"
#include "maplestation_modules\code\modules\loadouts\loadout_items\_limb_datums.dm"
#include "maplestation_modules\code\modules\loadouts\loadout_items\_loadout_categories.dm"
Expand Down Expand Up @@ -5632,6 +5633,9 @@
#include "maplestation_modules\code\modules\mob\living\carbon\human\human.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\modular_sechud_icons.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\skrell_hair.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\cpr.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\heart_attack.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\heart_overrides.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\armwings.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\avian_tails.dm"
#include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\plumage.dm"
Expand Down
5 changes: 4 additions & 1 deletion maplestation_modules/code/__DEFINES/_module_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
#define TOOLTIP_NO_DAMAGE "This item has very low force and is largely cosmetic."
#define TOOLTIP_RANDOM_COLOR "This item has a random color and will change every round."

/// Modular traits
// Modular traits
/// Provides additional resistance to contracting diseases. Should be removed next upstream
#define TRAIT_DISEASE_RESISTANT "disease_resistant"
/// Does not harm patients when undergoing CPR
#define TRAIT_CPR_CERTIFIED "cpr_certified"

/// Bitflags for speech sounds
#define SOUND_NORMAL (1<<0)
Expand Down
7 changes: 7 additions & 0 deletions maplestation_modules/code/datums/quirks/neutral.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@
//Will add named cardbinder, starting base 2 cards, packbox of 28 Red cards, 4 counters, and a paper with rules. Now in a handy box!
give_item_to_holder(card_binder, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS))
give_item_to_holder(/obj/item/storage/box/tdatet_starter, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS))

/datum/quirk/cpr_certified
name = "CPR Certified"
desc = "You are certified to perform CPR on others independent of your job."
icon = FA_ICON_HEARTBEAT
value = 0
mob_trait = TRAIT_CPR_CERTIFIED
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// -- Quartermaster locker stuff. --
/obj/structure/closet/secure_closet/quartermaster/PopulateContents()
. = ..()
new /obj/item/storage/box/skillchips/cargo(src)
new /obj/item/storage/bag/garment/magic/quartermaster(src) // done at the veeeery end for a reason.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/obj/item/skillchip/entrails_reader
complexity = 0 // who cares?

/// Teaches you how to perform CPR without harming the patient
/obj/item/skillchip/job/cpr
name = "CPR-Aid"
skill_name = "CPR Training"
skill_description = "Provides the user with the knowledge of how to safely perform CPR on a patient."
skill_icon = FA_ICON_HEARTBEAT
activate_message = span_notice("You feel more confident in your ability to perform CPR.")
deactivate_message = span_notice("You suddenly feel less confident in your ability to perform CPR.")
auto_traits = list(TRAIT_CPR_CERTIFIED)
complexity = 0 // it's pretty minor, all things considered

/datum/outfit/job/doctor/New()
. = ..()
LAZYADD(skillchips, /obj/item/skillchip/job/cpr)

/datum/outfit/job/paramedic/New()
. = ..()
LAZYADD(skillchips, /obj/item/skillchip/job/cpr)

/datum/outfit/job/cmo/New()
. = ..()
LAZYADD(skillchips, /obj/item/skillchip/job/cpr)

/obj/item/storage/box/skillchips/medbay
name = "box of medbay skillchips"
desc = "Contains spares of every medical job skillchip."

/obj/item/storage/box/skillchips/medbay/PopulateContents()
new /obj/item/skillchip/job/cpr(src)
new /obj/item/skillchip/job/cpr(src)
new /obj/item/skillchip/job/cpr(src)
new /obj/item/skillchip/job/cpr(src)
new /obj/item/skillchip/entrails_reader(src)

/obj/structure/closet/secure_closet/chief_medical/PopulateContents()
. = ..()
new /obj/item/storage/box/skillchips/medbay(src)

/// Book that grants the same trait as a skillchip, since reasonably there's no need for it to be chip locked
/obj/item/book/granter/cpr
name = "CPR Training Manual"
desc = "A book that teaches you how to perform CPR properly and safely."
icon_state = "book7"
remarks = list(
"Assess the situation for danger... But it's always dangerous on the station!",
"Make sure to check for a pulse and call for help before starting...",
"Thirty compressions followed by two breaths...",
"Match compressions to the beat of 'Staying Alive'... What?",
"Proper hand placement is crucial. Wait, are lizardperson hearts in the same location as humans?",
"Tilt the head back and pinch the nose before delivering breaths.",
"Let the chest return to its normal position after each compression.",
"Follow up with an AED if available.",
)
pages_to_mastery = 6
reading_time = 2 SECONDS
uses = INFINITY

/obj/item/book/granter/cpr/can_learn(mob/living/user)
return !HAS_TRAIT_FROM(user, TRAIT_CPR_CERTIFIED, name)

/obj/item/book/granter/cpr/on_reading_start(mob/living/user)
. = ..()
if(HAS_TRAIT(user, TRAIT_CPR_CERTIFIED))
to_chat(user, span_notice("You already know how to perform CPR, but it can't hurt to brush up."))

/obj/item/book/granter/cpr/on_reading_finished(mob/living/user)
. = ..()
if(HAS_TRAIT(user, TRAIT_CPR_CERTIFIED))
to_chat(user, span_green("You remind yourself of the proper way to perform CPR safely."))
else
to_chat(user, span_green("You feel confident in your ability to perform CPR safely."))

ADD_TRAIT(user, TRAIT_CPR_CERTIFIED, name)
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
skill_name = "Off Station Pain Resistance"
skill_description = "For the adventurous in life, this skillchip provides a reduction in pain received when off the station."
skill_icon = "fist-raised"
activate_message = "<span class='notice'>You feel like you can safely take on the unknown.</span>"
deactivate_message = "<span class='notice'>You feel more vulnerable to the unknown.</span>"
activate_message = span_notice("You feel like you can safely take on the unknown.")
deactivate_message = span_notice("You feel more vulnerable to the unknown.")

/obj/item/skillchip/job/off_z_pain_resistance/on_activate(mob/living/carbon/user, silent = FALSE)
. = ..()
Expand Down Expand Up @@ -47,3 +47,7 @@
/obj/item/storage/box/skillchips/cargo/PopulateContents()
new /obj/item/skillchip/job/off_z_pain_resistance(src)
new /obj/item/skillchip/job/off_z_pain_resistance(src)

/obj/structure/closet/secure_closet/quartermaster/PopulateContents()
. = ..()
new /obj/item/storage/box/skillchips/cargo(src)
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@

/mob/living/carbon/human/do_cpr(mob/living/carbon/human/target)
if(target == src)
return FALSE

if (DOING_INTERACTION_WITH_TARGET(src, target))
return FALSE

cpr_process(target, beat = 1) // begin at beat 1, skip the first breath
return TRUE

/// Number of "beats" per CPR cycle
/// This corresponds to N - 1 compressions and 1 breath
#define BEATS_PER_CPR_CYCLE 16
// Also I'm kinda cheating here because we do 15 compressions to 1 breath rather than 30 compressions to 2 breaths
// But it's close enough to the real thing (ratio wise) that I'm OK with it

/mob/living/carbon/human/proc/cpr_process(mob/living/carbon/human/target, beat = 0, panicking = FALSE)
set waitfor = FALSE

if (!target.appears_alive())
to_chat(src, span_warning("[target.name] is dead!"))
return

if(!panicking && target.stat != CONSCIOUS && beat >= BEATS_PER_CPR_CYCLE + 1)
to_chat(src, span_warning("[target] still isn't up[HAS_TRAIT(src, TRAIT_CPR_CERTIFIED) ? " - you pick up the pace." : "! You try harder!"]"))
panicking = TRUE

var/doafter_mod = panicking ? 0.5 : 1

var/doing_a_breath = FALSE
if(beat % BEATS_PER_CPR_CYCLE == 0)
if (is_mouth_covered())
to_chat(src, span_warning("Your mouth is covered, so you can only perform compressions!"))

else if (target.is_mouth_covered())
to_chat(src, span_warning("[p_their(TRUE)] mouth is covered, so you can only perform compressions!"))

else if (!get_organ_slot(ORGAN_SLOT_LUNGS))
to_chat(src, span_warning("You have no lungs to breathe with, so you can only perform compressions!"))

else if (HAS_TRAIT(src, TRAIT_NOBREATH))
to_chat(src, span_warning("You do not breathe, so you can only perform compressions!"))

else
doing_a_breath = TRUE

if(doing_a_breath)
visible_message(
span_notice("[src] attempts to give [target.name] a rescue breath!"),
span_notice("You attempt to give [target.name] a rescue breath as a part of CPR... Hold still!"),
)

if(!do_after(user = src, delay = doafter_mod * 6 SECONDS, target = target))
return

add_mood_event("saved_life", /datum/mood_event/saved_life)
visible_message(
span_notice("[src] delivers a rescue breath on [target.name]!"),
span_notice("You deliver a rescue breath on [target.name]."),
)

else
var/is_first_compression = beat % BEATS_PER_CPR_CYCLE == 1
if(is_first_compression)
visible_message(
span_notice("[src] attempts to give [target.name] chest compressions!"),
span_notice("You try to perform chest compressions as a part of CPR on [target.name]... Hold still!"),
)

if(!do_after(user = src, delay = doafter_mod * 1 SECONDS, target = target))
return

if(is_first_compression)
visible_message(
span_notice("[src] delivers chest compressions on [target.name]!"),
span_notice("You deliver chest compressions on [target.name]."),
)

target.apply_status_effect(/datum/status_effect/cpr_applied)

if(doing_a_breath)
if(HAS_TRAIT(target, TRAIT_NOBREATH))
to_chat(target, span_unconscious("You feel a breath of fresh air... which is a sensation you don't recognise..."))
else if (!target.get_organ_slot(ORGAN_SLOT_LUNGS))
to_chat(target, span_unconscious("You feel a breath of fresh air... but you don't feel any better..."))
else if(HAS_TRAIT(src, TRAIT_CPR_CERTIFIED))
target.adjustOxyLoss(-20)
to_chat(target, span_unconscious("You feel a breath of fresh air enter your lungs... It feels good..."))
else
target.adjustOxyLoss(-12)
to_chat(target, span_unconscious("You feel a breath of fresh air enter your lungs..."))

// Breath relieves some of the pressure on the chest
var/obj/item/bodypart/chest/chest = target.get_bodypart(BODY_ZONE_CHEST)
if(IS_ORGANIC_LIMB(chest))
to_chat(target, span_notice("You feel the pressure on your chest ease!"))
chest.heal_damage(brute = 3)
target.cause_pain(BODY_ZONE_CHEST, -2)

log_combat(src, target, "CPRed", addition = "(breath)")

else if(beat % (BEATS_PER_CPR_CYCLE / 4) == 0 && panicking)
var/obj/item/bodypart/chest/chest = target.get_bodypart(BODY_ZONE_CHEST)
if(IS_ORGANIC_LIMB(chest))
var/critical_success = prob(1) && target.undergoing_cardiac_arrest()
if(!HAS_TRAIT(src, TRAIT_CPR_CERTIFIED))
// Apply damage directly to chest. I would use apply damage but I can't, kinda
if(critical_success)
target.set_heartattack(FALSE)
to_chat(target, span_warning("You feel immense pressure on your chest, and a sudden wave of pain... and then relief."))
chest.receive_damage(brute = 6, wound_bonus = CANT_WOUND, damage_source = "chest compressions")
target.cause_pain(BODY_ZONE_CHEST, 12)

else
to_chat(target, span_warning("You feel pressure on your chest!"))
chest.receive_damage(brute = 3, wound_bonus = CANT_WOUND, damage_source = "chest compressions")
target.cause_pain(BODY_ZONE_CHEST, 2)

to_chat(src, span_warning("You bruise [target.name]'s chest with the pressure!"))

else if(critical_success)
target.set_heartattack(FALSE)
to_chat(target, span_warning("You pressure fade away from your chest... and then relief."))
target.cause_pain(BODY_ZONE_CHEST, 8)

log_combat(src, target, "CPRed", addition = "(compression)")

if(target.body_position != LYING_DOWN)
return
if(target.stat == CONSCIOUS)
return

cpr_process(target, beat + 1, panicking)

#undef BEATS_PER_CPR_CYCLE

/datum/status_effect/cpr_applied
id = "cpr"
alert_type = null
duration = 1 SECONDS
tick_interval = -1
status_type = STATUS_EFFECT_REFRESH

/datum/status_effect/cpr_applied/on_apply()
if(!is_effective(owner))
return FALSE
return TRUE

/datum/status_effect/cpr_applied/refresh(effect, ...)
if(!is_effective(owner))
return
return ..()

/// Checks if CPR is effective against this mob
/datum/status_effect/cpr_applied/proc/is_effective(mob/checking)
if(isnull(checking))
return FALSE
if(!checking.get_organ_slot(ORGAN_SLOT_HEART)) // A heart is required for CPR to pump your heart
return FALSE
return TRUE
Loading

0 comments on commit 602823b

Please sign in to comment.