diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 531ff4dd937..05bb9fe13ea 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -1016,6 +1016,8 @@ #define COMSIG_HUMAN_SPECIES_CHANGED "human_species_changed" /// Source: /mob/living/carbon/human/handle_environment(datum/gas_mixture/environment) #define COMSIG_HUMAN_EARLY_HANDLE_ENVIRONMENT "human_early_handle_environment" +/// 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/proc/check_shields(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration, damage_type) #define COMSIG_HUMAN_CHECK_SHIELDS "human_check_shields" diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index cc35d430294..f51475ec50e 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -388,7 +388,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) diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 951ed9c1f51..44bc7a00dcb 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -140,6 +140,7 @@ #define span_adminticket(str) ("" + str + "") #define span_error(str) ("" + str + "") #define span_gamesay(str) ("" + str + "") +#define span_whisper(str) ("" + str + "") #define span_wingdings(str) ("" + str + "") #define span_disarm(str) ("" + str + "") #define span_heavybrass(str) ("" + str + "") diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 3baf94e3708..e8234efd019 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -209,3 +209,6 @@ /// Makes you lean on something #define STATUS_EFFECT_LEANING /datum/status_effect/leaning + +/// This status effect allows a mob to be revived with a defibrillator. +#define STATUS_EFFECT_REVIVABLE /datum/status_effect/limited_bonus/revivable diff --git a/code/datums/components/defibrillator.dm b/code/datums/components/defibrillator.dm index 9456b0dcfe3..2b2d9d43fae 100644 --- a/code/datums/components/defibrillator.dm +++ b/code/datums/components/defibrillator.dm @@ -227,7 +227,7 @@ // Run through some quick failure states after shocking. var/time_dead = world.time - target.timeofdeath - if((time_dead > DEFIB_TIME_LIMIT) || !target.get_organ_slot(INTERNAL_ORGAN_HEART)) + if(!target.is_revivable() || !target.get_organ_slot(INTERNAL_ORGAN_HEART)) defib_ref.atom_say("Реанимация не удалась - обнаружены необратимые повреждения сердца!") defib_success = FALSE else if(target.getBruteLoss() >= 180 || target.getFireLoss() >= 180 || target.getCloneLoss() >= 180) @@ -258,7 +258,7 @@ target.heal_damages(tox = heal_amount, oxy = 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) @@ -276,6 +276,10 @@ SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100) if(ishuman(target.pulledby)) // for some reason, pulledby isnt a list despite it being possible to be pulled by multiple people excess_shock(user, target, target.pulledby, defib_ref) + if(target.receiving_cpr_from) + var/mob/living/carbon/human/H = locateUID(target.receiving_cpr_from) + if(istype(H)) + excess_shock(user, target, H, defib_ref) target.med_hud_set_health() target.med_hud_set_status() diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index f32221f66fb..7de472866a2 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -179,6 +179,59 @@ 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 + +/datum/status_effect/limited_bonus/revivable/on_creation(mob/living/new_owner, ...) + . = ..() + + RegisterSignal(owner, COMSIG_HUMAN_RECEIVE_CPR, PROC_REF(on_cpr)) + RegisterSignal(owner, COMSIG_LIVING_REVIVE, PROC_REF(on_revive)) + owner.med_hud_set_status() // update revivability after adding the status effect + +/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/adaptive_learning id = "adaptive_learning" duration = 300 diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index af1d2d2586d..b1ed249de9d 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -207,7 +207,7 @@ var/mob/living/simple_animal/borer/B = has_brain_worms() // To the right of health bar if(stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH)) - var/revivable = timeofdeath && (round(world.time - timeofdeath) < DEFIB_TIME_LIMIT) + var/revivable = timeofdeath && is_revivable() if(!ghost_can_reenter() || suiciding) // DNR or AntagHUD or Suicide revivable = FALSE if(revivable) diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 007ea0067c2..484f2cc2221 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -605,7 +605,7 @@ REAGENT SCANNER if(H.timeofdeath) data["timeofdeath"] = "[station_time_timestamp("hh:mm:ss", H.timeofdeath)]" var/tdelta = round(world.time - H.timeofdeath) - if(tdelta < DEFIB_TIME_LIMIT && !DNR) + if(H.is_revivable() && !DNR) data["timetodefib"] = "[DisplayTimeText(tdelta)]" data["timetodefibText"] = "Дефибриляция возможна!" else @@ -811,7 +811,7 @@ REAGENT SCANNER if(H.timeofdeath && (H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH))) scan_data += "Время смерти: [station_time_timestamp("hh:mm:ss", H.timeofdeath)]" var/tdelta = round(world.time - H.timeofdeath) - if(tdelta < DEFIB_TIME_LIMIT && !DNR) + if(H.is_revivable() && !DNR) scan_data += " Субъект умер [DisplayTimeText(tdelta)] назад" scan_data += " Дефибриляция возможна!" else diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index b8b9798cd3e..b8f573bd5b3 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -417,6 +417,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp can_reenter_corpse = FALSE if(!QDELETED(mind.current)) // Could change while they're choosing + mind.current.remove_status_effect(STATUS_EFFECT_REVIVABLE) mind.current.med_hud_set_status() SEND_SIGNAL(mind.current, COMSIG_LIVING_SET_DNR) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index cf1e4cd59ec..3c174a32b64 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -47,19 +47,22 @@ /mob/living/carbon/alien/attack_hand(mob/living/carbon/human/M) if(..()) //to allow surgery to return properly. - return 0 + return FALSE switch(M.a_intent) if(INTENT_HELP) - help_shake_act(M) + if(M.on_fire) + pat_out(M) + else + help_shake_act(M) if(INTENT_GRAB) grabbedby(M) if(INTENT_HARM) M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) if(INTENT_DISARM) M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return 1 - return 0 + return TRUE + return FALSE /mob/living/carbon/alien/attack_animal(mob/living/simple_animal/M) @@ -70,7 +73,7 @@ /mob/living/carbon/alien/acid_act(acidpwr, acid_volume) - return 0 //aliens are immune to acid. + return FALSE //aliens are immune to acid. /mob/living/carbon/alien/attack_slime(mob/living/simple_animal/slime/M) if(..()) //successful slime attack diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 145af8fe9ab..7a3a8aae99b 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -238,22 +238,6 @@ span_notice("Вы трясёте [name], пытаясь разбудить [genderize_ru(gender, "его", "её", "его", "их")]."),\ ) - else if(on_fire) - var/self_message = span_warning("Вы пытаетесь потушить [name].") - if(prob(30) && ishuman(M)) // 30% chance of burning your hands - var/mob/living/carbon/human/H = M - var/protected = FALSE // Protected from the fire - if((H.gloves?.max_heat_protection_temperature > 360) || HAS_TRAIT(H, TRAIT_RESIST_HEAT)) - protected = TRUE - if(!protected) - H.apply_damage(5, BURN, def_zone = H.hand ? BODY_ZONE_PRECISE_L_HAND : BODY_ZONE_PRECISE_R_HAND) - self_message = span_danger("Вы обжигаете свои руки, пытаясь потушить [name]!") - H.update_icons() - - M.visible_message(span_warning("[M] пыта[pluralize_ru(M.gender, "ет", "ют")]ся потушить [name]."), self_message) - playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - adjust_fire_stacks(-0.5) - // BEGIN HUGCODE - N3X else playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) @@ -275,6 +259,34 @@ else if(H.w_uniform) H.w_uniform.add_fingerprint(M) +/** + * Handles patting out a fire on someone. + * + * Removes 0.5 fire stacks per pat, with a 30% chance of the user burning their hand if they don't have adequate heat resistance. + * Arguments: + * * src - The mob doing the patting + * * target - The mob who is currently on fire + */ +/mob/living/carbon/proc/pat_out(mob/living/target) + if(target == src) // stop drop and roll, no trying to put out fire on yourself for free. + to_chat(src, "Stop drop and roll!") + return + var/self_message = span_warning("Вы пытаетесь потушить [name].") + if(prob(30) && ishuman(src)) // 30% chance of burning your hands + var/mob/living/carbon/human/H = src + var/protected = FALSE // Protected from the fire + if((H.gloves?.max_heat_protection_temperature > 360) || HAS_TRAIT(H, TRAIT_RESIST_HEAT)) + protected = TRUE + + var/obj/item/organ/external/active_hand = H.get_active_hand() + if(active_hand && !protected) // Wouldn't really work without a hand + active_hand.external_receive_damage(0, 5) + self_message = span_danger("Вы обжигаете свои руки, пытаясь потушить [name]!") + H.update_icons() + + target.visible_message(span_warning("[target] пыта[pluralize_ru(target.gender, "ет", "ют")]ся потушить [name]."), self_message) + playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + target.adjust_fire_stacks(-0.5) /mob/living/carbon/proc/check_self_for_injuries() var/mob/living/carbon/human/H = src diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index cfea69526dd..9f85c4d6be4 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -51,6 +51,10 @@ if(!.) return FALSE + if(!gibbed && !HAS_TRAIT(src, TRAIT_FAKEDEATH)) + // We dont want people who have fake death to have the status effect applied a second time when they actually die + apply_status_effect(STATUS_EFFECT_REVIVABLE) + if(reagents) reagents.death_metabolize(src) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index a7cd92d68f8..c429d42bbd6 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -538,7 +538,7 @@ if(hasHUD(usr, EXAMINE_HUD_SECURITY_WRITE) && setcriminal != "Cancel") found_record = 1 if(R.fields["criminal"] == SEC_RECORD_STATUS_EXECUTE) - to_chat(usr, "Unable to modify the sec status of a person with an active Execution order. Use a security computer instead.") + to_chat(usr, span_warning("Unable to modify the sec status of a person with an active Execution order. Use a security computer instead.")) else var/rank if(ishuman(usr)) @@ -555,7 +555,7 @@ break // Git out of the general records if(!found_record) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["secrecord"]) if(hasHUD(usr, EXAMINE_HUD_SECURITY_READ)) @@ -579,7 +579,7 @@ read = 1 if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["secrecordComment"]) if(hasHUD(usr, EXAMINE_HUD_SECURITY_READ)) @@ -598,12 +598,12 @@ for(var/c in R.fields["comments"]) to_chat(usr, c) else - to_chat(usr, "No comments found") + to_chat(usr, span_warning("No comments found")) if(hasHUD(usr, EXAMINE_HUD_SECURITY_WRITE)) to_chat(usr, "\[Add comment\]") if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["secrecordadd"]) if(usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED) || !hasHUD(usr, EXAMINE_HUD_SECURITY_WRITE)) @@ -638,7 +638,7 @@ sec_hud_set_security_status() if(!modified) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["medrecord"]) if(hasHUD(usr, EXAMINE_HUD_MEDICAL)) @@ -663,7 +663,7 @@ read = 1 if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["medrecordComment"]) if(hasHUD(usr, EXAMINE_HUD_MEDICAL)) @@ -682,11 +682,11 @@ for(var/c in R.fields["comments"]) to_chat(usr, c) else - to_chat(usr, "No comment found") + to_chat(usr, span_warning("No comment found")) to_chat(usr, "\[Add comment\]") if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") + to_chat(usr, span_warning("Unable to locate a data core entry for this person.")) if(href_list["medrecordadd"]) if(usr.incapacitated() || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED) || !hasHUD(usr, EXAMINE_HUD_MEDICAL)) @@ -710,7 +710,7 @@ skills = E.fields["notes"] break if(skills) - to_chat(usr, "Employment records: [skills]\n") + to_chat(usr, span_deptradio("Employment records: [skills]\n")) if(href_list["lookitem"]) var/obj/item/I = locate(href_list["lookitem"]) @@ -779,7 +779,7 @@ /mob/living/carbon/human/proc/play_xylophone() if(!src.xylophone) - visible_message("[src] begins playing [p_their()] ribcage like a xylophone. It's quite spooky.","You begin to play a spooky refrain on your ribcage.","You hear a spooky xylophone melody.") + visible_message(span_warning("[src] begins playing [p_their()] ribcage like a xylophone. It's quite spooky."), span_notice("You begin to play a spooky refrain on your ribcage."), span_warning("You hear a spooky xylophone melody.")) var/song = pick('sound/effects/xylophone1.ogg','sound/effects/xylophone2.ogg','sound/effects/xylophone3.ogg') playsound(loc, song, 50, 1, -1) xylophone = 1 @@ -813,7 +813,7 @@ if(!. && error_msg && user) if(!fail_msg) fail_msg = "There is no exposed flesh or thin material [target_zone == BODY_ZONE_HEAD ? "on [p_their()] head" : "on [p_their()] body"] to inject into." - to_chat(user, "[fail_msg]") + to_chat(user, span_alert("[fail_msg]")) /mob/living/carbon/human/check_obscured_slots(check_transparent) @@ -961,16 +961,16 @@ if(usr == src) self = 1 if(!self) - usr.visible_message("[usr] kneels down, puts [usr.p_their()] hand on [src]'s wrist and begins counting [p_their()] pulse.",\ + usr.visible_message(span_notice("[usr] kneels down, puts [usr.p_their()] hand on [src]'s wrist and begins counting [p_their()] pulse."),\ "You begin counting [src]'s pulse") else - usr.visible_message("[usr] begins counting [p_their()] pulse.",\ + usr.visible_message(span_notice("[usr] begins counting [p_their()] pulse."),\ "You begin counting your pulse.") if(src.pulse) - to_chat(usr, "[self ? "You have a" : "[src] has a"] pulse! Counting...") + to_chat(usr, span_notice("[self ? "You have a" : "[src] has a"] pulse! Counting...")) else - to_chat(usr, "[src] has no pulse!")//it is REALLY UNLIKELY that a dead person would check his own pulse + to_chat(usr, span_warning("[src] has no pulse!"))//it is REALLY UNLIKELY that a dead person would check his own pulse return @@ -980,7 +980,7 @@ if(usr.l_move_time >= time) //checks if our mob has moved during the sleep() to_chat(usr, "You moved while counting. Try again.") else - to_chat(usr, "[self ? "Your" : "[src]'s"] pulse is [src.get_pulse(GETPULSE_HAND)].") + to_chat(usr, span_notice("[self ? "Your" : "[src]'s"] pulse is [src.get_pulse(GETPULSE_HAND)].")) /** @@ -1272,18 +1272,18 @@ if(usr != src) return 0 //something is terribly wrong if(incapacitated()) - to_chat(src, "You can't write on the floor in your current state!") + to_chat(src, span_warning("You can't write on the floor in your current state!")) return if(!bloody_hands) remove_verb(src, /mob/living/carbon/human/proc/bloody_doodle) if(gloves) - to_chat(src, "[gloves] are preventing you from writing anything down!") + to_chat(src, span_warning("[gloves] are preventing you from writing anything down!")) return var/turf/simulated/T = loc if(!istype(T)) //to prevent doodling out of mechs and lockers - to_chat(src, "You cannot reach the floor.") + to_chat(src, span_warning("You cannot reach the floor.")) return var/turf/origin = T @@ -1291,21 +1291,21 @@ if(direction != "Here") T = get_step(T,text2dir(direction)) if(!istype(T)) - to_chat(src, "You cannot doodle there.") + to_chat(src, span_warning("You cannot doodle there.")) return var/num_doodles = 0 for(var/obj/effect/decal/cleanable/blood/writing/W in T) num_doodles++ if(num_doodles > 4) - to_chat(src, "There is no space to write on!") + to_chat(src, span_warning("There is no space to write on!")) return var/max_length = bloody_hands * 30 //tweeter style var/message = tgui_input_text(src, "Write a message. It cannot be longer than [max_length] characters.", "Blood writing", max_length = max_length) if(origin != loc) - to_chat(src, "Stay still while writing!") + to_chat(src, span_notice("Stay still while writing!")) return if(message) var/used_blood_amount = round(length(message) / 30, 1) @@ -1313,9 +1313,9 @@ if(length(message) > max_length) message += "-" - to_chat(src, "You ran out of blood to write with!") + to_chat(src, span_warning("You ran out of blood to write with!")) else - to_chat(src, "You daub '[message]' on [T] in shiny red lettering.") + to_chat(src, span_notice("You daub '[message]' on [T] in shiny red lettering.")) var/obj/effect/decal/cleanable/blood/writing/W = new(T) W.message = message W.add_fingerprint(src) @@ -1451,7 +1451,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X for(var/obj/item/hand in handlist) if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && drop_item_ground(hand)) step_towards(hand, src) - to_chat(src, "\The [S] pulls \the [hand] from your grip!") + to_chat(src, span_warning("\The [S] pulls \the [hand] from your grip!")) apply_effect(current_size * 3, IRRADIATE) /mob/living/carbon/human/narsie_act(obj/singularity/god/narsie/narsie) @@ -1466,45 +1466,145 @@ Eyes need to have significantly high darksight to shine unless the mob has the X ratvar.soul_devoured++ . = ..() + +#define CPR_CHEST_COMPRESSION_ONLY 0.75 +#define CPR_RESCUE_BREATHS 1 +#define CPR_START_HEART_CHANCE 25 +#define CPR_CHEST_COMPRESSION_RESTORATION (1 SECONDS) +#define CPR_BREATHS_RESTORATION (3 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, "You cannot perform CPR on yourself!") + to_chat(src, span_warning("You cannot perform CPR on yourself!")) return - if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH)) - to_chat(src, "[H.name] is dead!") + if(!isnull(H.receiving_cpr_from)) // To prevent spam stacking + to_chat(src, span_warning("They are already receiving CPR!")) return - if(!check_has_mouth()) - to_chat(src, "You don't have a mouth, you cannot perform CPR!") - return - if(!H.check_has_mouth()) - to_chat(src, "They don't have a mouth, you cannot perform CPR!") + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) || !has_both_hands() || pulling && pull_hand != PULL_WITHOUT_HANDS) + to_chat(src, span_warning("You need two hands available to do CPR!")) return - if((head && (head.flags_cover & HEADCOVERSMOUTH)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && !wear_mask.up)) - to_chat(src, "Remove your mask first!") + if(l_hand || r_hand) + to_chat(src, span_warning("You can't perform effective CPR with your hands full!")) return - if((H.head && (H.head.flags_cover & HEADCOVERSMOUTH)) || (H.wear_mask && (H.wear_mask.flags_cover & MASKCOVERSMOUTH) && !H.wear_mask.up)) - to_chat(src, "Remove [H.p_their()] mask first!") - return - if(H.receiving_cpr) // To prevent spam stacking - to_chat(src, "They are already receiving CPR!") + + H.receiving_cpr_from = UID() + var/cpr_modifier = get_cpr_mod(H) + if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH)) + if(ismachineperson(H) && do_after(src, 4 SECONDS, H, NONE)) // hehe + visible_message( + span_warning("[src] bangs [p_their()] head on [H]'s chassis by accident!"), + span_danger("You go in for a rescue breath, and bang your head on [H]'s machine chassis. CPR's not going to work.") + ) + playsound(H, 'sound/weapons/ringslam.ogg', 50, TRUE) + apply_damage(2, BRUTE, BODY_ZONE_HEAD) + H.receiving_cpr_from = null + return + + if(!H.is_revivable()) + to_chat(src, span_warning("[H] is already too far gone for CPR...")) + H.receiving_cpr_from = null + return + + visible_message(span_danger("[src] is trying to perform CPR on [H]'s lifeless body!"), span_danger("You start trying to perform CPR on [H]'s lifeless body!")) + while(do_after(src, 4 SECONDS, H, NONE) && (H.stat == DEAD || HAS_TRAIT(H, TRAIT_FAKEDEATH)) && H.is_revivable()) + var/timer_restored + if(cpr_modifier == CPR_CHEST_COMPRESSION_ONLY) + visible_message(span_notice("[src] gives [H] chest compressions."), span_notice("You can't make rescue breaths work, so you do your best to give chest compressions.")) + timer_restored = CPR_CHEST_COMPRESSION_RESTORATION // without rescue breaths, it won't stave off the death timer forever + else + visible_message(span_notice("[src] gives [H] chest compressions and rescue breaths."), span_notice("You give [H] chest compressions and rescue breaths.")) + 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_userdanger("Your chest burns as you receive unnecessary CPR!")) + continue + + SEND_SIGNAL(H, COMSIG_HUMAN_RECEIVE_CPR, timer_restored) + + if(prob(5)) + if(timer_restored >= CPR_BREATHS_RESTORATION) + to_chat(src, pick(effective_cpr_messages)) + else + to_chat(src, pick(ineffective_cpr_messages)) + + cpr_try_activate_bomb(H) + + + if(!H.is_revivable()) + to_chat(src, span_notice("You feel [H]'s body is already starting to stiffen beneath you...it's too late for CPR now.")) + else + visible_message(span_notice("[src] stops giving [H] CPR."), span_notice("You stop giving [H] CPR.")) + + H.receiving_cpr_from = null return - visible_message("[src] is trying to perform CPR on [H.name]!", "You try to perform CPR on [H.name]!") - H.receiving_cpr = TRUE - if(do_after(src, 4 SECONDS, H, NONE)) - if(H.health <= HEALTH_THRESHOLD_CRIT) - H.heal_damage_type(15, OXY) - H.SetLoseBreath(0) - H.AdjustParalysis(-2 SECONDS) - visible_message("[src] performs CPR on [H.name]!", "You perform CPR on [H.name].") - - to_chat(H, "You feel a breath of fresh air enter your lungs. It feels good.") - H.receiving_cpr = FALSE - add_attack_logs(src, H, "CPRed", ATKLOG_ALL) - return TRUE - else - H.receiving_cpr = FALSE - to_chat(src, "You need to stay still while performing CPR!") + visible_message(span_danger("[src] is trying to perform CPR on [H.name]!"), span_danger("You try to perform CPR on [H.name]!")) + + if(cpr_modifier == CPR_CHEST_COMPRESSION_ONLY) + to_chat(src, span_warning("You can't get to [H]'s mouth, so your CPR will be less effective!")) + + while(do_after(src, 4 SECONDS, H, NONE) && H.health <= HEALTH_THRESHOLD_CRIT) + H.adjustOxyLoss(-15 * cpr_modifier) + H.SetLoseBreath(0) + H.AdjustParalysis(-2 SECONDS) + H.updatehealth("cpr") + visible_message(span_danger("[src] performs CPR on [H.name]!"), span_notice("You perform CPR on [H.name].")) + + if(prob(CPR_START_HEART_CHANCE)) + set_heartattack(FALSE) + + cpr_try_activate_bomb(H) + + if(cpr_modifier == CPR_RESCUE_BREATHS) + to_chat(H, span_notice("You feel a breath of fresh air enter your lungs. It feels good.")) + + add_attack_logs(src, H, "CPRed", ATKLOG_ALL) + + H.receiving_cpr_from = null + visible_message(span_notice("[src] stops performing CPR on [H]."), span_notice("You stop performing CPR on [H].")) + to_chat(src, span_danger("You need to stay still while performing CPR!")) + +/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_NO_BREATH) || HAS_TRAIT(H, TRAIT_NO_BREATH)) + return CPR_CHEST_COMPRESSION_ONLY + + if(dna?.species.breathid != H.dna?.species.breathid) // sorry non oxy-breathers + to_chat(src, span_warning("You don't think you'd be able to give [H] very effective rescue breaths...")) + return CPR_CHEST_COMPRESSION_ONLY + return CPR_RESCUE_BREATHS + +/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_notice("You feel something click under your hands.")) + 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_START_HEART_CHANCE +#undef CPR_CHEST_COMPRESSION_RESTORATION +#undef CPR_BREATHS_RESTORATION /mob/living/carbon/human/has_mutated_organs() for(var/obj/item/organ/external/E as anything in bodyparts) @@ -1795,7 +1895,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X /mob/living/carbon/human/get_spooked() - to_chat(src, "[pick(GLOB.boo_phrases)]") + to_chat(src, span_whisper("[pick(GLOB.boo_phrases)]")) return TRUE /mob/living/carbon/human/extinguish_light(force = FALSE) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 040145b9b64..b26e19cb5cd 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -75,7 +75,8 @@ var/bleedsuppress = 0 //for stopping bloodloss var/heartbeat = 0 - var/receiving_cpr = FALSE + /// UID of the person who is giving this mob CPR. + var/receiving_cpr_from var/datum/body_accessory/body_accessory = null /// Name of tail image in species effects icon file. diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm index 5697cb4ee95..964f851666a 100644 --- a/code/modules/mob/living/carbon/human/species/_species.dm +++ b/code/modules/mob/living/carbon/human/species/_species.dm @@ -487,7 +487,9 @@ /datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) if(attacker_style && attacker_style.help_act(user, target) == TRUE)//adminfu only... return TRUE - if(target.health >= HEALTH_THRESHOLD_CRIT && !HAS_TRAIT(target, TRAIT_FAKEDEATH)) + if(target.on_fire) + user.pat_out(target) + else if(target.health >= HEALTH_THRESHOLD_CRIT && !HAS_TRAIT(target, TRAIT_FAKEDEATH) && target.stat != DEAD) target.help_shake_act(user) return TRUE else diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 762380b619b..badd6087f5f 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -80,8 +80,6 @@ update_stamina_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) for(var/s in ownedSoullinks) var/datum/soullink/S = s diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm index 1f3b8ad74e8..06864332a28 100644 --- a/code/modules/mob/living/init_signals.dm +++ b/code/modules/mob/living/init_signals.dm @@ -70,12 +70,14 @@ /mob/living/proc/on_fakedeath_trait_gain(datum/source) SIGNAL_HANDLER ADD_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_FAKEDEATH) + apply_status_effect(STATUS_EFFECT_REVIVABLE) /// Called when [TRAIT_FAKEDEATH] is removed from the mob. /mob/living/proc/on_fakedeath_trait_loss(datum/source) SIGNAL_HANDLER REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_FAKEDEATH) + remove_status_effect(STATUS_EFFECT_REVIVABLE) /// Called when [TRAIT_IMMOBILIZED] is added to the mob. diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index b9b2db9bafa..5bac1d553a0 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -910,6 +910,12 @@ force_gene_block(block, FALSE) +// Revivability + +/mob/living/proc/is_revivable() + return has_status_effect(STATUS_EFFECT_REVIVABLE) + + ///Unignores all slowdowns that lack the IGNORE_NOSLOW flag. /mob/living/proc/unignore_slowdown(source) REMOVE_TRAIT(src, TRAIT_IGNORESLOWDOWN, source) diff --git a/sound/weapons/ringslam.ogg b/sound/weapons/ringslam.ogg new file mode 100644 index 00000000000..5c2d7f339b6 Binary files /dev/null and b/sound/weapons/ringslam.ogg differ