From e0043fc2ea24fbb2d95cd6daf4282c9f76e79e7d Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Sat, 1 Jul 2023 22:02:30 +0200
Subject: [PATCH] [MIRROR] Useful Stethoscopes and Penlights [MDB IGNORE]
(#22183)
* Useful Stethoscopes and Penlights (#75800)
## About The Pull Request
This PR overhauls and adds functionality to Stethoscopes and Flashlight
examinations (i.e. shining a penlight into someone's eyes/mouth).
Stethoscope Changes
- Dynamic results based on targeted body part.
- Chest: Assess whether the heart is beating, whether it is damaged
(above 10 dmg), and whether or not the player has suffered severe blood
loss. Assess whether the player is breathing, has lung damage (above
10dmg), and whether or not they have suffocation damage (above 10dmg).
- Head/Extremities: Assess heart function as above, and blood level
(nominal/low/critical).
- Groin: Assess whether or not the liver or the appendix have suffered
damage (above 10dmg). (The latter is only detectable if the patient is
conscious)
Flashlight Changes
- Eyes: Assess whether the patient is dead, has sustained brain damage
(above 20dmg), or has an X-Ray mutation.
- Mouth: Assess mouth organs, dental implants, suffocation damage (above
20dmg), and blood volume (nominal/low/critical)
## Why It's Good For The Game
Adds some proper functionality to two very rarely used tools.
Loosens up medical players' dependence on health analyzers to assess
their patients Provides an alternative, if manual and less accurate,
means of assessment.
For players interested in a more hands-on, RP focused approach to medbay
(or those in areas without power to recharge analyzers); I think this
will be a welcome addition.
## Proof of Testing
Screenshots/Videos
https://github.com/tgstation/tgstation/assets/107971606/43e87774-6db5-4db1-b7fa-92c3565c1009
https://github.com/tgstation/tgstation/assets/107971606/9ec42cc4-a996-4d60-a6f1-1aecf24b9bc8
## Changelog
:cl:
add: Stethoscopes may be used on the chest, groin, or extremities to
assess organ damage, blood level, and/or suffocation damage depending on
the targeted area.
add: Shining flashlights into the mouth or eyes of other players will
additionally assess brain health, suffocation damage, and/or blood level
depending on the targeted area.
balance: Halves the duration of the flash effect from shining lights
into players' eyes (2s -> 1s). Use combat mode to get the full duration.
/:cl:
---------
Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
Co-authored-by: Jacquerel
* Useful Stethoscopes and Penlights
---------
Co-authored-by: LovliestPlant <107971606+LovliestPlant@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
Co-authored-by: Jacquerel
---
code/game/objects/items/devices/flashlight.dm | 94 ++++++++++----
code/modules/clothing/neck/_neck.dm | 116 ++++++++++++++++--
2 files changed, 173 insertions(+), 37 deletions(-)
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 192606f191f..1cac5747c14 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -112,6 +112,8 @@
to_chat(user, "[span_warning("\The [src] isn't bright enough to see anything!")] ")
return
+ var/render_list = list()//information will be packaged in a list for clean display to the user
+
switch(user.zone_selected)
if(BODY_ZONE_PRECISE_EYES)
if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES))
@@ -119,24 +121,38 @@
return
var/obj/item/organ/internal/eyes/E = M.get_organ_slot(ORGAN_SLOT_EYES)
+ var/obj/item/organ/internal/brain = M.get_organ_slot(ORGAN_SLOT_BRAIN)
if(!E)
to_chat(user, span_warning("[M] doesn't have any eyes!"))
return
+ M.flash_act(visual = TRUE, length = (user.combat_mode) ? 2.5 SECONDS : 1 SECONDS) // Apply a 1 second flash effect to the target. The duration increases to 2.5 Seconds if you have combat mode on.
+
if(M == user) //they're using it on themselves
- if(M.flash_act(visual = 1))
- M.visible_message(span_notice("[M] directs [src] to [M.p_their()] eyes."), span_notice("You wave the light in front of your eyes! Trippy!"))
+ user.visible_message(span_warning("[user] shines [src] into [M.p_their()] eyes."), ignored_mobs = user)
+ render_list += span_info("You direct [src] to into your eyes:\n")
+
+ if(M.is_blind())
+ render_list += "You're not entirely certain what you were expecting...\n"
else
- M.visible_message(span_notice("[M] directs [src] to [M.p_their()] eyes."), span_notice("You wave the light in front of your eyes."))
+ render_list += "Trippy!\n"
+
else
- user.visible_message(span_warning("[user] directs [src] to [M]'s eyes."), \
- span_danger("You direct [src] to [M]'s eyes."))
- if(M.stat == DEAD || (M.is_blind()) || !M.flash_act(visual = 1)) //mob is dead or fully blind
- to_chat(user, span_warning("[M]'s pupils don't react to the light!"))
- else if(M.dna && M.dna.check_mutation(/datum/mutation/human/xray)) //mob has X-ray vision
- to_chat(user, span_danger("[M]'s pupils give an eerie glow!"))
- else //they're okay!
- to_chat(user, span_notice("[M]'s pupils narrow."))
+ user.visible_message(span_warning("[user] directs [src] to [M]'s eyes."), ignored_mobs = user)
+ render_list += span_info("You direct [src] to [M]'s eyes:\n")
+
+ if(M.stat == DEAD || M.is_blind())
+ render_list += "[M.p_their(TRUE)] pupils don't react to the light!\n"//mob is dead
+ else if(brain.damage > 20)
+ render_list += "[M.p_their(TRUE)] pupils contract unevenly!\n"//mob has sustained damage to their brain
+ else
+ render_list += "[M.p_their(TRUE)] pupils narrow.\n"//they're okay :D
+
+ if(M.dna && M.dna.check_mutation(/datum/mutation/human/xray))
+ render_list += "[M.p_their(TRUE)] pupils give an eerie glow!\n"//mob has X-ray vision
+
+ //display our packaged information in an examine block for easy reading
+ to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
if(BODY_ZONE_PRECISE_MOUTH)
@@ -144,8 +160,6 @@
to_chat(user, span_warning("You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSMOUTH) ? "helmet" : "mask"] first!"))
return
- var/their = M.p_their()
-
var/list/mouth_organs = new
for(var/obj/item/organ/organ as anything in M.organs)
if(organ.zone == BODY_ZONE_PRECISE_MOUTH)
@@ -166,7 +180,7 @@
for(var/datum/action/item_action/hands_free/activate_pill/AP in M.actions)
pill_count++
- if(M == user)
+ if(M == user)//if we're looking on our own mouth
var/can_use_mirror = FALSE
if(isturf(user.loc))
var/obj/structure/mirror/mirror = locate(/obj/structure/mirror, user.loc)
@@ -181,27 +195,57 @@
if(WEST)
can_use_mirror = mirror.pixel_x < 0
- M.visible_message(span_notice("[M] directs [src] to [their] mouth."), \
- span_notice("You point [src] into your mouth."))
+ M.visible_message(span_notice("[M] directs [src] to [ M.p_their()] mouth."), ignored_mobs = user)
+ render_list += span_info("You point [src] into your mouth:\n")
if(!can_use_mirror)
to_chat(user, span_notice("You can't see anything without a mirror."))
return
if(organ_count)
- to_chat(user, span_notice("Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list]."))
+ render_list += "Inside your mouth [organ_count > 1 ? "are" : "is"] [organ_list].\n"
else
- to_chat(user, span_notice("There's nothing inside your mouth."))
+ render_list += "There's nothing inside your mouth.\n"
if(pill_count)
- to_chat(user, span_notice("You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""]."))
+ render_list += "You have [pill_count] implanted pill[pill_count > 1 ? "s" : ""].\n"
- else
- user.visible_message(span_notice("[user] directs [src] to [M]'s mouth."),\
- span_notice("You direct [src] to [M]'s mouth."))
+ else //if we're looking in someone elses mouth
+ user.visible_message(span_notice("[user] directs [src] to [M]'s mouth."), ignored_mobs = user)
+ render_list += span_info("You point [src] into [M]'s mouth:\n")
if(organ_count)
- to_chat(user, span_notice("Inside [their] mouth [organ_count > 1 ? "are" : "is"] [organ_list]."))
+ render_list += "Inside [ M.p_their()] mouth [organ_count > 1 ? "are" : "is"] [organ_list].\n"
else
- to_chat(user, span_notice("[M] doesn't have any organs in [their] mouth."))
+ render_list += "[M] doesn't have any organs in [ M.p_their()] mouth.\n"
if(pill_count)
- to_chat(user, span_notice("[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [their] teeth."))
+ render_list += "[M] has [pill_count] pill[pill_count > 1 ? "s" : ""] implanted in [ M.p_their()] teeth.\n"
+
+ //assess any suffocation damage
+ var/hypoxia_status = M.getOxyLoss() > 20
+
+ if(M == user)
+ if(hypoxia_status)
+ render_list += "Your lips appear blue!\n"//you have suffocation damage
+ else
+ render_list += "Your lips appear healthy.\n"//you're okay!
+ else
+ if(hypoxia_status)
+ render_list += "[M.p_their(TRUE)] lips appear blue!\n"//they have suffocation damage
+ else
+ render_list += "[M.p_their(TRUE)] lips appear healthy.\n"//they're okay!
+
+ //assess blood level
+ if(M == user)
+ render_list += span_info("You press a finger to your gums:\n")
+ else
+ render_list += span_info("You press a finger to [M.p_their()] gums:\n")
+
+ if(M.blood_volume <= BLOOD_VOLUME_SAFE && M.blood_volume > BLOOD_VOLUME_OKAY)
+ render_list += "Color returns slowly!\n"//low blood
+ else if(M.blood_volume <= BLOOD_VOLUME_OKAY)
+ render_list += "Color does not return!\n"//critical blood
+ else
+ render_list += "Color returns quickly.\n"//they're okay :D
+
+ //display our packaged information in an examine block for easy reading
+ to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
else
return ..()
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index e01fd3bb353..dc8e5623267 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -185,26 +185,118 @@
var/mob/living/carbon/carbon_patient = M
var/body_part = parse_zone(user.zone_selected)
+ var/oxy_loss = carbon_patient.getOxyLoss()
- var/heart_strength = span_danger("no")
- var/lung_strength = span_danger("no")
+ var/heart_strength
+ var/pulse_pressure
var/obj/item/organ/internal/heart/heart = carbon_patient.get_organ_slot(ORGAN_SLOT_HEART)
var/obj/item/organ/internal/lungs/lungs = carbon_patient.get_organ_slot(ORGAN_SLOT_LUNGS)
+ var/obj/item/organ/internal/liver/liver = carbon_patient.get_organ_slot(ORGAN_SLOT_LIVER)
+ var/obj/item/organ/internal/appendix/appendix = carbon_patient.get_organ_slot(ORGAN_SLOT_APPENDIX)
- if(carbon_patient.stat != DEAD && !(HAS_TRAIT(carbon_patient, TRAIT_FAKEDEATH)))
- if(istype(heart))
- heart_strength = (heart.beating ? "a healthy" : span_danger("an unstable"))
- if(istype(lungs))
- lung_strength = ((carbon_patient.failed_last_breath || carbon_patient.losebreath) ? span_danger("strained") : "healthy")
+ var/render_list = list()//information will be packaged in a list for clean display to the user
- user.visible_message(span_notice("[user] places [src] against [carbon_patient]'s [body_part] and listens attentively."), ignored_mobs = user)
+ //determine what specific action we're taking
+ switch (body_part)
+ if(BODY_ZONE_CHEST)//Listening to the chest
+ user.visible_message(span_notice("[user] places [src] against [carbon_patient]'s [body_part] and listens attentively."), ignored_mobs = user)
+ if(!user.can_hear())
+ to_chat(user, span_notice("You place [src] against [carbon_patient]'s [body_part]. Fat load of good it does you though, since you can't hear"))
+ return
+ else
+ render_list += span_info("You place [src] against [carbon_patient]'s [body_part]:\n")
- var/diagnosis = (body_part == BODY_ZONE_CHEST ? "You hear [heart_strength] pulse and [lung_strength] respiration" : "You faintly hear [heart_strength] pulse")
- if(!user.can_hear())
- diagnosis = "Fat load of good it does you though, since you can't hear"
+ //assess breathing
+ if(!lungs)//sanity check, enusure patient actually has lungs
+ render_list += "[M] doesn't have any lungs!\n"
+ else
+ if(carbon_patient.stat == DEAD || (HAS_TRAIT(carbon_patient, TRAIT_FAKEDEATH)) || (HAS_TRAIT(carbon_patient, TRAIT_NOBREATH))|| carbon_patient.failed_last_breath || carbon_patient.losebreath)//If pt is dead or otherwise not breathing
+ render_list += "[M.p_theyre(TRUE)] not breathing!\n"
+ else if(lungs.damage > 10)//if breathing, check for lung damage
+ render_list += "You hear fluid in [M.p_their()] lungs!\n"
+ else if(oxy_loss > 10)//if they have suffocation damage
+ render_list += "[M.p_theyre(TRUE)] breathing heavily!\n"
+ else
+ render_list += "[M.p_theyre(TRUE)] breathing normally.\n"//they're okay :D
- to_chat(user, span_notice("You place [src] against [carbon_patient]'s [body_part]. [diagnosis]."))
+ //assess heart
+ if(body_part == BODY_ZONE_CHEST)//if we're listening to the chest
+ if(!heart)//sanity check, ensure the patient actually has a heart
+ render_list += "[M] doesn't have a heart!\n"
+ else
+ if(!heart.beating || carbon_patient.stat == DEAD)
+ render_list += "You don't hear a heartbeat!\n"//they're dead or their heart isn't beating
+ else if(heart.damage > 10 || carbon_patient.blood_volume <= BLOOD_VOLUME_OKAY)
+ render_list += "You hear a weak heartbeat.\n"//their heart is damaged, or they have critical blood
+ else
+ render_list += "You hear a healthy heartbeat.\n"//they're okay :D
+
+ if(BODY_ZONE_PRECISE_GROIN)//If we're targeting the groin
+ render_list += span_info("You carefully press down on [carbon_patient]'s abdomen:\n")
+ user.visible_message(span_notice("[user] presses their hands against [carbon_patient]'s abdomen."), ignored_mobs = user)
+
+ //assess abdominal organs
+ if(body_part == BODY_ZONE_PRECISE_GROIN)
+ var/appendix_okay = TRUE
+ var/liver_okay = TRUE
+ if(!liver)//sanity check, ensure the patient actually has a liver
+ render_list += "[M] doesn't have a liver!\n"
+ liver_okay = FALSE
+ else
+ if(liver.damage > 10)
+ render_list += "[M.p_their(TRUE)] liver feels firm.\n"//their liver is damaged
+ liver_okay = FALSE
+
+ if(!appendix)//sanity check, ensure the patient actually has an appendix
+ render_list += "[M] doesn't have an appendix!\n"
+ appendix_okay = FALSE
+ else
+ if(appendix.damage > 10 && carbon_patient.stat == CONSCIOUS)
+ render_list += "[M] screams when you lift your hand from [M.p_their()] appendix!\n"//scream if their appendix is damaged and they're awake
+ M.emote("scream")
+ appendix_okay = FALSE
+
+ if(liver_okay && appendix_okay)//if they have all their organs and have no detectable damage
+ render_list += "You don't find anything abnormal.\n"//they're okay :D
+
+ if(BODY_ZONE_PRECISE_EYES)
+ balloon_alert(user, "can't do that!")
+ return
+
+ if(BODY_ZONE_PRECISE_MOUTH)
+ balloon_alert(user, "can't do that!")
+ return
+
+ else//targeting an extremity or the head
+ if(body_part == BODY_ZONE_HEAD)
+ render_list += span_info("You carefully press your fingers to [carbon_patient]'s neck:\n")
+ user.visible_message(span_notice("[user] presses their fingers against [carbon_patient]'s neck."), ignored_mobs = user)
+ else
+ render_list += span_info("You carefully press your fingers to [carbon_patient]'s [body_part]:\n")
+ user.visible_message(span_notice("[user] presses their fingers against [carbon_patient]'s [body_part]."), ignored_mobs = user)
+
+ //assess pulse (heart & blood level)
+ if(!heart)//sanity check, ensure the patient actually has a heart
+ render_list += "[M] doesn't have a heart!\n"
+ else
+ if(!heart.beating || carbon_patient.blood_volume <= BLOOD_VOLUME_OKAY || carbon_patient.stat == DEAD)
+ render_list += "You can't find a pulse!\n"//they're dead, their heart isn't beating, or they have critical blood
+ else
+ if(heart.damage > 10)
+ heart_strength = span_danger("irregular")//their heart is damaged
+ else
+ heart_strength = span_notice("regular")//they're okay :D
+
+ if(carbon_patient.blood_volume <= BLOOD_VOLUME_SAFE && carbon_patient.blood_volume > BLOOD_VOLUME_OKAY)
+ pulse_pressure = span_danger("thready")//low blood
+ else
+ pulse_pressure = span_notice("strong")//they're okay :D
+
+ render_list += "[M.p_their(TRUE)] pulse is [pulse_pressure] and [heart_strength].\n"
+
+ //display our packaged information in an examine block for easy reading
+ to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
///////////
//SCARVES//