diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm index 30e72353a5..f8c1d4f08d 100644 --- a/code/game/objects/items/devices/defib.dm +++ b/code/game/objects/items/devices/defib.dm @@ -289,21 +289,30 @@ /obj/item/shockpaddles/proc/can_revive(mob/living/carbon/human/H) //This is checked right before attempting to revive var/obj/item/organ/internal/brain/brain = H.internal_organs_by_name[O_BRAIN] - if(H.should_have_organ(O_BRAIN) && (!brain || (istype(brain) && brain.defib_timer <= 0 ) ) ) //CHOMPEdit - Fix a runtime when brain is an MMI - return "buzzes, \"Resuscitation failed - Excessive neural degeneration. Further attempts futile.\"" + if(H.should_have_organ(O_BRAIN)) + if(!brain) + return "buzzes, \"Resuscitation failed - Patient lacks a brain. Further attempts futile without replacement.\"" + if(brain.defib_timer <= 0) + return "buzzes, \"Resuscitation failed - Patient's brain has naturally degraded past a recoverable state. Further attempts futile.\"" H.updatehealth() if(H.isSynthetic()) if(H.health + H.getOxyLoss() + H.getToxLoss() <= CONFIG_GET(number/health_threshold_dead)) - return "buzzes, \"Resuscitation failed - Severe damage detected. Begin manual repair before further attempts futile.\"" + return "buzzes, \"Resuscitation failed - Severe damage detected. Begin damage restoration before further attempts.\"" - else if(H.health + H.getOxyLoss() <= CONFIG_GET(number/health_threshold_dead) || (HUSK in H.mutations) || !H.can_defib) - return "buzzes, \"Resuscitation failed - Severe tissue damage makes recovery of patient impossible via defibrillator. Further attempts futile.\"" + else if(H.health + H.getOxyLoss() <= CONFIG_GET(number/health_threshold_dead)) //They need to be healed first. + return "buzzes, \"Resuscitation failed - Severe tissue damage detected. Repair of anatomical damage required.\"" - var/bad_vital_organ = check_vital_organs(H) + else if(HUSK in H.mutations) //Husked! Need to fix their husk status first. + return "buzzes, \"Resuscitation failed - Anatomical structure malformation detected. 'De-Husk' surgery required.\"" + + else if(!H.can_defib) //We can frankensurgery them! Let's tell the user. + return "buzzes, \"Resuscitation failed - Severe neurological deformation detected. Brain-stem reattachment surgery required.\"" + + var/bad_vital_organ = H.check_vital_organs() //CONTRARY to what you may think, your HEART AND LUNGS ARE NOT VITAL. Only the brain is. This is here in case a species has a special vital organ they need to survive in addiition to their brain. if(bad_vital_organ) - return bad_vital_organ + return "buzzes, \"Resuscitation failed - Patient's ([bad_vital_organ]) is missing / suffering extensive damage. Further attempts futile without surgical intervention.\"" //this needs to be last since if any of the 'other conditions are met their messages take precedence //if(!H.client && !H.teleop) @@ -319,17 +328,11 @@ return TRUE /obj/item/shockpaddles/proc/check_vital_organs(mob/living/carbon/human/H) - for(var/organ_tag in H.species.has_organ) - var/obj/item/organ/O = H.species.has_organ[organ_tag] - var/name = initial(O.name) - var/vital = initial(O.vital) //check for vital organs - if(vital) - O = H.internal_organs_by_name[organ_tag] - if(!O) - return "buzzes, \"Resuscitation failed - Patient is missing vital organ ([name]). Further attempts futile.\"" - if(O.damage > O.max_damage) - return "buzzes, \"Resuscitation failed - Excessive damage to vital organ ([name]). Further attempts futile.\"" - return null + var/bad_vital = H.check_vital_organs() + if(!bad_vital) //All organs are A-OK. Let's go! + return null + //Otherwise, we have a bad vital organ, return a message to the user + return "buzzes, \"Resuscitation failed - Patient is vital organ ([bad_vital]) is missing / suffering extensive damage. Further attempts futile without surgical intervention.\"" /obj/item/shockpaddles/proc/check_blood_level(mob/living/carbon/human/H) if(!H.should_have_organ(O_HEART)) diff --git a/code/modules/mob/_modifiers/medical.dm b/code/modules/mob/_modifiers/medical.dm index dabcd60d86..df365dd6d2 100644 --- a/code/modules/mob/_modifiers/medical.dm +++ b/code/modules/mob/_modifiers/medical.dm @@ -3,6 +3,8 @@ * Modifiers applied by Medical sources. */ +//See blood.dm. This makes your blood volume & raw blood volume set to 100%. +//This means (as long as you have blood) you will not suffocate. Even with no heart or lungs. /datum/modifier/bloodpump name = "external blood pumping" desc = "Your blood flows thanks to the wonderful power of science." @@ -28,15 +30,30 @@ pulse_set_level = PULSE_SLOW -/datum/modifier/bloodpump/corpse/check_if_valid() +/datum/modifier/bloodpump_corpse/check_if_valid() ..() if(holder.stat != DEAD) src.expire() +//This INTENTIONALLY only happens on DEAD people. Alive people are metabolizing already (and can be healed quicker through things like brute packs) meaning they don't need this extra assistance! +//Why does it not make you bleed out? Because we'll let medical have a few benefits that don't come with innate downsides. It takes 2 seconds to resleeve someone. It takes a good amount of time to repair a corpse. Let's make the latter more appealing. +/datum/modifier/bloodpump_corpse/tick() + var/i = 5 //It's a controlled machine. It pumps at a nice, steady rate. + while(i-- > 0) + holder.handle_chemicals_in_body() // Circulates chemicals throughout the body. /* * Modifiers caused by chemicals or organs specifically. */ +/datum/modifier/bloodpump_corpse/cpr + desc = "Your blood flows thanks to the wonderful power of CPR." + pulse_set_level = PULSE_NONE //No pulse. You're acting as their pulse. + +/datum/modifier/bloodpump_corpse/tick() + var/i = rand(4,7) //CPR isn't perfect. You get some randomization in there. + while(i-- > 0) + holder.handle_chemicals_in_body() // Circulates chemicals throughout the body. + /datum/modifier/cryogelled name = "cryogelled" desc = "Your body begins to freeze." diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index ad6d632598..77c416cea8 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1681,6 +1681,23 @@ return 0 return (species && species.has_organ[organ_check]) +/// Checks our organs and sees if we are missing anything vital, or if it is too heavily damaged +/// Returns two values: +/// FALSE if all our vital organs are intact +/// Or the name of the organ if we are missing a vital organ / it is damaged beyond repair. +/mob/living/carbon/human/proc/check_vital_organs() + for(var/organ_tag in species.has_organ) + var/obj/item/organ/O = species.has_organ[organ_tag] + var/name = initial(O.name) + var/vital = initial(O.vital) //check for vital organs + if(vital) + O = internal_organs_by_name[organ_tag] + if(!O) + return name + if(O.damage > O.max_damage) + return name + return FALSE + /mob/living/carbon/human/can_feel_pain(var/obj/item/organ/check_organ) if(isSynthetic()) return 0 diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index f920f47831..0869790808 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -144,10 +144,7 @@ H.visible_message(span_danger("\The [H] performs CPR on \the [src]!")) to_chat(H, span_warning("Repeat at least every 7 seconds.")) - if(istype(H) && health > CONFIG_GET(number/health_threshold_dead)) - adjustOxyLoss(-(min(getOxyLoss(), 5))) - updatehealth() - to_chat(src, span_notice("You feel a breath of fresh air enter your lungs. It feels good.")) + perform_cpr(H) else if(!(M == src && apply_pressure(M, M.zone_sel.selecting))) help_shake_act(M) @@ -557,3 +554,81 @@ /mob/living/carbon/human/proc/set_default_attack(var/datum/unarmed_attack/u_attack) default_attack = u_attack + + +/mob/living/carbon/human/proc/perform_cpr(var/mob/living/carbon/human/reviver) + // Check for sanity + if(!istype(reviver,/mob/living/carbon/human)) + return + //The below is what actually allows metabolism. + add_modifier(/datum/modifier/bloodpump_corpse/cpr, 2 SECONDS) + + // Toggle for 'realistic' CPR. Use this if you want a more grim CPR approach that mimicks the damage that CPR can do to someone. This means more extensive internal damage, almost guaranteed rib breakage, etc. + // DEFAULT: FALSE + var/realistic_cpr = FALSE + + // brute damage + if(prob(3)) + apply_damage(1, BRUTE, BP_TORSO) + if(prob(25) || (realistic_cpr)) //This being a 25% chance on top of the 3% chance means you have a 0.75% chance every compression to break ribs (and do minor internal damage). Realism mode means it's a 100% chance every time that 3% procs. + var/obj/item/organ/external/chest = get_organ(BP_TORSO) + if(chest) + chest.fracture() + + // standard CPR ahead, adjust oxy and refresh health + if(health > CONFIG_GET(number/health_threshold_crit) && prob(10)) + if(istype(species, /datum/species/xenochimera)) + visible_message(span_danger("\The [src]'s body twitches and gurgles a bit.")) + to_chat(reviver, span_danger("You get the feeling [src] can't be revived by CPR alone.")) + return // Handle xenochim, can't cpr them back to life + if(HUSK in mutations) + visible_message(span_danger("\The [src]'s body crunches and snaps.")) + to_chat(reviver, span_danger("You get the feeling [src] is going to need surgical intervention to be revived.")) + return // Handle husked, cure it before you can revive + if(!can_defib) + visible_message(span_danger("\The [src]'s neck shifts and cracks!")) + to_chat(reviver, span_danger("You get the feeling [src] is going to need surgical intervention to be revived.")) + return // Handle broken neck/no attached brain + var/bad_vital_organ = check_vital_organs() + if(bad_vital_organ) + visible_message(span_danger("\The [src]'s body lays completely limp and lifeless!")) + to_chat(reviver, span_danger("You get the feeling [src] is missing something vital.")) + return // Handle vital organs being missing. + + // allow revive chance + var/mob/observer/dead/ghost = get_ghost() + if(ghost) + ghost.notify_revive("Someone is trying to resuscitate you. Re-enter your body if you want to be revived!", 'sound/effects/genetics.ogg', source = src) + visible_message(span_warning("\The [src]'s body convulses a bit.")) + + // REVIVE TIME, basically stolen from defib.dm + dead_mob_list.Remove(src) + if((src in living_mob_list) || (src in dead_mob_list)) + WARNING("Mob [src] was cpr revived by [reviver], but already in the living or dead list still!") + living_mob_list += src + + timeofdeath = 0 + set_stat(UNCONSCIOUS) //Life() can bring them back to consciousness if it needs to. + failed_last_breath = 0 //So mobs that died of oxyloss don't revive and have perpetual out of breath. + reload_fullscreen() + + emote("gasp") + Weaken(rand(10,25)) + updatehealth() + //SShaunting.influence(HAUNTING_RESLEEVE) // Used for the Haunting module downstream. Not implemented upstream. + + // This is measures in `Life()` ticks. E.g. 10 minute defib timer = 300 `Life()` ticks. // Original math was VERY off. Life() tick occurs every ~2 seconds, not every 2 world.time ticks. + var/brain_damage_timer = ((CONFIG_GET(number/defib_timer) MINUTES) / 20) - ((CONFIG_GET(number/defib_braindamage_timer) MINUTES) / 20) + var/obj/item/organ/internal/brain/brain = internal_organs_by_name[O_BRAIN] + if(should_have_organ(O_BRAIN) && brain && brain.defib_timer <= brain_damage_timer) + // As the brain decays, this will be between 0 and 1, with 1 being the most fresh. + var/brain_death_scale = brain.defib_timer / brain_damage_timer + // This is backwards from what you might expect, since 1 = fresh and 0 = rip. + var/damage_calc = LERP(brain.max_damage, getBrainLoss(), brain_death_scale) + // A bit of sanity. + var/brain_damage = between(getBrainLoss(), damage_calc, brain.max_damage) + setBrainLoss(brain_damage) + else if(health > CONFIG_GET(number/health_threshold_dead)) + adjustOxyLoss(-(min(getOxyLoss(), 5))) + updatehealth() + to_chat(src, span_notice("You feel a breath of fresh air enter your lungs. It feels good.")) diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 9e25fa42ba..79861718c4 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -1,6 +1,6 @@ //Updates the mob's health from organs and mob damage variables /mob/living/carbon/human/updatehealth() - var/huskmodifier = 2.5 //VOREStation Edit // With 1.5, you need 250 burn instead of 200 to husk a human. + var/huskmodifier = 2.5 // With 1.5, you need 250 burn instead of 200 to husk a human. if(status_flags & GODMODE) health = getMaxHealth() diff --git a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm index f85851e11d..ae4b08941d 100644 --- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm +++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm @@ -46,5 +46,5 @@ if(arguments) A.arguments_to_use = arguments ability_objects.Add(A) - if(my_mob.client) + if(my_mob && my_mob.client) //If a shadekin is made (mannequins) prior to initialize being finished, my_mob won't be assigned and this will runtime. Mannequins need massive fixing because they shouldn't be getting all these special huds and overlays when they don't need them. toggle_open(2) //forces the icons to refresh on screen diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm index 5d06adda82..477501b7ce 100644 --- a/code/modules/organs/organ_external.dm +++ b/code/modules/organs/organ_external.dm @@ -386,7 +386,9 @@ var/modifed_burn = burn // Let's calculate how INJURED our limb is accounting for AFTER the damage we just took. Determines the chance the next attack will take our limb off! - var/damage_factor = ((max_damage*CONFIG_GET(number/organ_health_multiplier))/(brute_dam + burn_dam))*100 + var/damage_factor = ((brute_dam + burn_dam)/(max_damage*CONFIG_GET(number/organ_health_multiplier)))*100 + if(brute_dam > max_damage || burn_dam > max_damage) //This is in case we go OVER our max. This doesn't EVER happen except on VITAL organs. + damage_factor = 100 // Max_damage of 80 and brute_dam of 80? || Factor = 100 Max_damage of 80 and brute_dam of 40? Factor = 50 || Max_damage of 80 and brute_dam of 5? Factor = 5 // This lowers our chances of having our limb removed when it has less damage. The more damaged the limb, the higher the chance it falls off! @@ -1143,7 +1145,7 @@ Note that amputating the affected organ does in fact remove the infection from t span_danger("You hear a sickening crack.")),brokenpain) //CHOMPEdit End owner.emote("scream") - jostle_bone() //VOREStation Edit End + jostle_bone() if(istype(owner.loc, /obj/belly)) //CHOMPedit, bone breaks in bellys should be whisper range to prevent bar wide blender prefbreak. This is a hacky passive hardcode, if a pref gets added, remove this if else playsound(src, "fracture", 90, 1, -6.5) diff --git a/code/modules/reagents/reagents/_reagents.dm b/code/modules/reagents/reagents/_reagents.dm index cf727ceb58..6608e97f86 100644 --- a/code/modules/reagents/reagents/_reagents.dm +++ b/code/modules/reagents/reagents/_reagents.dm @@ -23,7 +23,7 @@ var/can_overdose_touch = FALSE // Can the chemical OD when processing on touch? var/scannable = 0 // Shows up on health analyzers. - var/affects_dead = 0 // Does this chem process inside a corpse? + var/affects_dead = 0 // Does this chem process inside a corpse without outside intervention required? var/affects_robots = 0 // Does this chem process inside a Synth? var/allergen_type // What potential allergens does this contain? @@ -67,7 +67,7 @@ /datum/reagent/proc/on_mob_life(var/mob/living/carbon/M, var/alien, var/datum/reagents/metabolism/location) // Currently, on_mob_life is called on carbons. Any interaction with non-carbon mobs (lube) will need to be done in touch_mob. if(!istype(M)) return - if(!affects_dead && M.stat == DEAD) + if(!affects_dead && M.stat == DEAD && !M.has_modifier_of_type(/datum/modifier/bloodpump_corpse)) return if(M.isSynthetic() && (!M.synth_reag_processing || !affects_robots)) //CHOMPEdit return diff --git a/code/modules/reagents/reagents/medicine.dm b/code/modules/reagents/reagents/medicine.dm index 056af17e16..10948ab92f 100644 --- a/code/modules/reagents/reagents/medicine.dm +++ b/code/modules/reagents/reagents/medicine.dm @@ -420,18 +420,14 @@ color = "#6b4de3" metabolism = REM * 0.5 mrate_static = TRUE + affects_dead = FALSE //Clarifying this here since the original intent was this ONLY works on people that have the bloodpump_corpse modifier. scannable = 1 /datum/reagent/mortiferin/on_mob_life(var/mob/living/carbon/M, var/alien, var/datum/reagents/metabolism/location) - if(M.stat == DEAD && M.has_modifier_of_type(/datum/modifier/bloodpump_corpse)) - affects_dead = TRUE - else - affects_dead = FALSE - . = ..(M, alien, location) /datum/reagent/mortiferin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) - if(M.bodytemperature < (T0C - 10) || (M.stat == DEAD && M.has_modifier_of_type(/datum/modifier/bloodpump_corpse))) + if(M.bodytemperature < (T0C - 10) || (M.stat == DEAD)) var/chem_effective = 1 * M.species.chem_strength_heal if(alien == IS_SLIME) if(prob(10)) diff --git a/code/modules/tgui/states/living.dm b/code/modules/tgui/states/living.dm new file mode 100644 index 0000000000..2c60ca1dfa --- /dev/null +++ b/code/modules/tgui/states/living.dm @@ -0,0 +1,17 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + +/** + * tgui state: living_state + * + * Checks that the user is a mob/living. + **/ + +GLOBAL_DATUM_INIT(tgui_living_state, /datum/tgui_state/living_state, new) + +/datum/tgui_state/living_state/can_use_topic(src_object, mob/user) + if(isliving(user)) + return STATUS_INTERACTIVE + return STATUS_CLOSE diff --git a/code/modules/tgui/states/living_adjacent.dm b/code/modules/tgui/states/living_adjacent.dm new file mode 100644 index 0000000000..59973eb436 --- /dev/null +++ b/code/modules/tgui/states/living_adjacent.dm @@ -0,0 +1,21 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + +/** + * tgui state: living_adjacent_state + * + * In addition to default checks, only allows interaction for a + * living adjacent user. + **/ + +GLOBAL_DATUM_INIT(tgui_living_adjacent_state, /datum/tgui_state/living_adjacent_state, new) + +/datum/tgui_state/living_adjacent_state/can_use_topic(src_object, mob/user) + . = user.default_can_use_tgui_topic(src_object) + + var/dist = get_dist(src_object, user) + if((dist > 1) || (!isliving(user))) + // Can't be used unless adjacent and human, even with TK + . = min(., STATUS_UPDATE) diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 5b7ec76c49..7ba5edf97b 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -1478,7 +1478,7 @@ if(should_proceed_with_revive) dead_mob_list.Remove(H) if((H in living_mob_list) || (H in dead_mob_list)) - WARNING("Mob [H] was defibbed but already in the living or dead list still!") + WARNING("Mob [H] was reformed but already in the living or dead list still!") living_mob_list += H H.timeofdeath = 0 diff --git a/code/modules/xenoarcheaology/tools/equipment.dm b/code/modules/xenoarcheaology/tools/equipment.dm index 126d1bc186..0dd6b34348 100644 --- a/code/modules/xenoarcheaology/tools/equipment.dm +++ b/code/modules/xenoarcheaology/tools/equipment.dm @@ -23,7 +23,7 @@ icon_state = "cespace_suit" item_state = "cespace_suit" armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 100, rad = 100) - allowed = list(POCKET_GENERIC, POCKET_EMERGENCY, POCKET_SUIT_REGULATORS, POCKET_MINING) + allowed = list(POCKET_GENERIC, POCKET_EMERGENCY, POCKET_SUIT_REGULATORS, POCKET_MINING, POCKET_ALL_TANKS) slowdown = 1 // Pressure protection inherited from space suits diff --git a/maps/common/common_things.dm b/maps/common/common_things.dm index a3f6e9f887..bec22e1e18 100644 --- a/maps/common/common_things.dm +++ b/maps/common/common_things.dm @@ -151,13 +151,12 @@ return ..() /obj/machinery/cryopod/robot/door/tram/Bumped(var/atom/movable/AM) - if(!ishuman(AM)) + if(!isliving(AM)) return - var/mob/living/carbon/human/user = AM - - var/choice = tgui_alert(user, "Do you want to depart via the tram? Your character will leave the round.","Departure",list("Yes","No")) - if(user && Adjacent(user) && choice == "Yes") + var/mob/living/user = AM + var/choice = tgui_alert(user, "Do you want to depart via the tram? Your character will leave the round.","Departure",list("Yes","No"), timeout = 5 SECONDS) + if(!QDELETED(user) && Adjacent(user) && choice == "Yes") var/mob/observer/dead/newghost = user.ghostize() newghost.timeofdeath = world.time despawn_occupant(user) diff --git a/vorestation.dme b/vorestation.dme index df48e855ed..777c3b6003 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -4551,6 +4551,8 @@ #include "code\modules\tgui\states\human_adjacent.dm" #include "code\modules\tgui\states\inventory.dm" #include "code\modules\tgui\states\inventory_vr.dm" +#include "code\modules\tgui\states\living.dm" +#include "code\modules\tgui\states\living_adjacent.dm" #include "code\modules\tgui\states\mentor.dm" #include "code\modules\tgui\states\not_incapacitated.dm" #include "code\modules\tgui\states\notcontained.dm"