diff --git a/modular_bandastation/medical/_medical.dme b/modular_bandastation/medical/_medical.dme new file mode 100644 index 0000000000000..a0104a291d805 --- /dev/null +++ b/modular_bandastation/medical/_medical.dme @@ -0,0 +1,48 @@ +#include "code/additional_med_component.dm" +#include "code/additional_med_mob.dm" + +#include "code/pain/overrides.dm" + +#include "code/mood/moodlets.dm" + +#include "code/additional_med_defines.dm" +#include "code/additional_med_functions.dm" +#include "code/additional_med_states.dm" + +#include "code/new_chemicals/unique_chemicals.dm" +#include "code/new_chemicals/organ_chemicals.dm" +#include "code/new_chemicals/painkillers.dm" + +#include "code/cpr_extension/autocompressor.dm" +#include "code/cpr_extension/cpr_extension.dm" + +#include "code/organ_damage/remake_organ_decay.dm" +#include "code/organ_damage/organ_damage_ondamage.dm" +#include "code/organ_damage/on_limb_damage.dm" +#include "code/organ_damage/organ_damage_toxin.dm" +#include "code/organ_damage/organ_damage_oxygen.dm" + +#include "code/new_wounds/internal_bleed.dm" +#include "code/new_wounds/necrosis.dm" +#include "code/new_wounds/wounds_remake.dm" + +#include "code/new_surgery/steps_remake.dm" +#include "code/new_surgery/surgery_iternalbleed.dm" +#include "code/new_surgery/organ_surgery_fails.dm" +#include "code/new_surgery/surgery_fail_chance.dm" + +#include "code/new_technologies/medical_designs.dm" + +#include "code/medical_items/iv_bags.dm" +#include "code/medical_items/medical_pen.dm" +#include "code/medical_items/chem_bag.dm" +#include "code/medical_items/field_kit.dm" + +#include "code/new_quirks/professional_doctor.dm" +#include "code/new_quirks/professional_surgeon.dm" +#include "code/new_skillchips/surgery_chip_chance.dm" + +#include "code/machines/surgery_tables.dm" +#include "code/machines/surgery_table_v1.dm" +#include "code/machines/surgery_table_v2.dm" +#include "code/machines/surgery_table_v3.dm" diff --git a/modular_bandastation/medical/code/additional_med_component.dm b/modular_bandastation/medical/code/additional_med_component.dm new file mode 100644 index 0000000000000..07cbae474be24 --- /dev/null +++ b/modular_bandastation/medical/code/additional_med_component.dm @@ -0,0 +1,140 @@ +#define ADVMED_PAIN_THRESHOLD_MAX 150 +#define ADVMED_PAIN_PART_MAX 200 +#define ADVMED_PAIN_EFFECT_MODIFIER 2 +#define ADVMED_PAIN_CHAT_LIMBS_LOW 10 +#define ADVMED_PAIN_CHAT_LIMBS_MIDDLE 30 +#define ADVMED_PAIN_CHAT_LIMBS_HIGH 50 +#define ADVMED_PAIN_EFFECT_LIMBS 50 +#define ADVMED_PAIN_EFFECT_CHEST 75 +#define ADVMED_PAIN_EFFECT_HEAD 25 +#define ADVMED_PAIN_MIN_MODIFIER 0.5 +#define ADVMED_PAIN_FADE 0.5 +#define ADVMED_PAIN_APPLICATION_MODIFIER 1.5 +#define ADVMED_PAIN_HEART_ATTACK_MODIFIER 2 + +/datum/component/additional_med + dupe_mode = COMPONENT_DUPE_UNIQUE + var/mob/living/carbon/assigned_mob = null + +/datum/component/additional_med/Initialize(list/arguments) + . = ..() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + assigned_mob = arguments + START_PROCESSING(SSobj, src) + +/datum/component/additional_med/process() + var/damaged_parts_list = assigned_mob.get_damaged_bodyparts(TRUE, TRUE, ALL) + var/min_pain = 0 + var/all_pain = 0 + var/add_pain = 0 + for(var/obj/item/bodypart/part in damaged_parts_list) + min_pain = (part.burn_dam + part.brute_dam) * ADVMED_PAIN_MIN_MODIFIER + if (part.pain >= ADVMED_PAIN_PART_MAX) + part.pain = ADVMED_PAIN_PART_MAX + if (part.pain - ADVMED_PAIN_FADE >= min_pain) + part.pain -= ADVMED_PAIN_FADE + else + part.pain = min_pain + add_pain = part.pain + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/low)) + add_pain = part.pain * 0.67 + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/medium)) + add_pain = part.pain * 0.34 + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/high)) + add_pain = part.pain * 0.01 + if (HAS_TRAIT(assigned_mob, TRAIT_ANALGESIA)) + add_pain = 0 + all_pain += add_pain + + if (prob(0.1)) + switch(add_pain) + if(0.01 to ADVMED_PAIN_CHAT_LIMBS_LOW) + to_chat(assigned_mob, span_danger("You feel small pain in your [part.name]")) + if(ADVMED_PAIN_CHAT_LIMBS_LOW to ADVMED_PAIN_CHAT_LIMBS_MIDDLE) + to_chat(assigned_mob, span_danger("You feel pain in your [part.name] and looks like it's time to threat your wounds")) + if(ADVMED_PAIN_CHAT_LIMBS_MIDDLE to ADVMED_PAIN_CHAT_LIMBS_HIGH) + to_chat(assigned_mob, span_danger("You feel sever pain in your [part.name] and it's almost unberable")) + if(ADVMED_PAIN_CHAT_LIMBS_HIGH to INFINITY) + to_chat(assigned_mob, span_danger("You [part.name] is burning like hell from pain!")) + + if (part.body_zone != BODY_ZONE_HEAD && part.body_zone != BODY_ZONE_CHEST) + if ((add_pain >= (ADVMED_PAIN_EFFECT_LIMBS / ADVMED_PAIN_EFFECT_MODIFIER)) && (part.body_zone == BODY_ZONE_L_ARM || part.body_zone == BODY_ZONE_R_ARM )) + if (prob(add_pain - ADVMED_PAIN_EFFECT_LIMBS)) + var/obj/item/item + if (part == BODY_ZONE_L_ARM) + item = assigned_mob.get_held_items_for_side(LEFT_HANDS) + else + item = assigned_mob.get_held_items_for_side(RIGHT_HANDS) + assigned_mob.dropItemToGround(item, TRUE) + switch(add_pain) + if(ADVMED_PAIN_EFFECT_LIMBS / ADVMED_PAIN_EFFECT_MODIFIER to ADVMED_PAIN_EFFECT_LIMBS / (ADVMED_PAIN_EFFECT_MODIFIER / 2)) + assigned_mob.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/pain/low) + if(ADVMED_PAIN_EFFECT_LIMBS / (ADVMED_PAIN_EFFECT_MODIFIER / 2) to ADVMED_PAIN_EFFECT_LIMBS) + assigned_mob.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/pain/midlow) + if(ADVMED_PAIN_EFFECT_LIMBS to ADVMED_PAIN_EFFECT_LIMBS * (ADVMED_PAIN_EFFECT_MODIFIER / 2)) + assigned_mob.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/pain/mid) + if(ADVMED_PAIN_EFFECT_LIMBS * (ADVMED_PAIN_EFFECT_MODIFIER / 2) to ADVMED_PAIN_EFFECT_LIMBS * ADVMED_PAIN_EFFECT_MODIFIER) + assigned_mob.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/pain/midhigh) + if(ADVMED_PAIN_EFFECT_LIMBS * ADVMED_PAIN_EFFECT_MODIFIER to INFINITY) + assigned_mob.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/pain/high) + if ((add_pain >= (ADVMED_PAIN_EFFECT_LIMBS / ADVMED_PAIN_EFFECT_MODIFIER)) && (part.body_zone == BODY_ZONE_L_LEG || part.body_zone == BODY_ZONE_R_LEG )) + if (prob(add_pain - ADVMED_PAIN_EFFECT_LIMBS)) + assigned_mob.Knockdown(1 SECONDS) + switch(add_pain) + if(ADVMED_PAIN_EFFECT_LIMBS / ADVMED_PAIN_EFFECT_MODIFIER to ADVMED_PAIN_EFFECT_LIMBS / (ADVMED_PAIN_EFFECT_MODIFIER / 2)) + assigned_mob.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/pain/low) + if(ADVMED_PAIN_EFFECT_LIMBS / (ADVMED_PAIN_EFFECT_MODIFIER / 2) to ADVMED_PAIN_EFFECT_LIMBS) + assigned_mob.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/pain/midlow) + if(ADVMED_PAIN_EFFECT_LIMBS to ADVMED_PAIN_EFFECT_LIMBS * (ADVMED_PAIN_EFFECT_MODIFIER / 2)) + assigned_mob.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/pain/mid) + if(ADVMED_PAIN_EFFECT_LIMBS * (ADVMED_PAIN_EFFECT_MODIFIER / 2) to ADVMED_PAIN_EFFECT_LIMBS * ADVMED_PAIN_EFFECT_MODIFIER) + assigned_mob.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/pain/midhigh) + if(ADVMED_PAIN_EFFECT_LIMBS * ADVMED_PAIN_EFFECT_MODIFIER to INFINITY) + assigned_mob.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/pain/high) + if (part.body_zone == BODY_ZONE_HEAD) + if (add_pain >= (ADVMED_PAIN_EFFECT_HEAD / ADVMED_PAIN_EFFECT_MODIFIER)) + //при уровне боли от 0 до 90 накладывает визуальный эффект пульсирующего белого экрана по краям. КТГ (BLIND) + if (add_pain >= ADVMED_PAIN_EFFECT_HEAD && prob(add_pain - ADVMED_PAIN_EFFECT_HEAD)) + assigned_mob.Unconscious(40) + assigned_mob.Stun(100) + + for(var/obj/item/organ/internal/organ in assigned_mob.organs) + min_pain = organ.damage * ADVMED_PAIN_MIN_MODIFIER + if (organ.pain >= ADVMED_PAIN_PART_MAX) + organ.pain = ADVMED_PAIN_PART_MAX + if (organ.pain - ADVMED_PAIN_FADE >= min_pain) + organ.pain -= ADVMED_PAIN_FADE + else + organ.pain = min_pain + add_pain = organ.pain + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/low)) + add_pain = organ.pain * 0.67 + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/medium)) + add_pain = organ.pain * 0.34 + if (assigned_mob.has_status_effect(/datum/status_effect/painkiller/high)) + add_pain = organ.pain * 0.01 + if (HAS_TRAIT(assigned_mob, TRAIT_ANALGESIA)) + add_pain = 0 + all_pain += add_pain + + if (all_pain >= ADVMED_PAIN_THRESHOLD_MAX * ADVMED_PAIN_HEART_ATTACK_MODIFIER && prob((all_pain - ADVMED_PAIN_THRESHOLD_MAX * ADVMED_PAIN_HEART_ATTACK_MODIFIER) / 3)) + var/datum/disease/heart_failure/heart_attack = new(src) + to_chat(assigned_mob, span_danger("Your chest is bursting and hurts a lot")) + heart_attack.stage_prob = 5 + assigned_mob.ForceContractDisease(heart_attack) + assigned_mob.Unconscious(100) + assigned_mob.Stun(200) + + switch(all_pain) + if(ADVMED_PAIN_THRESHOLD_MAX / 4 to ADVMED_PAIN_THRESHOLD_MAX / 2) + assigned_mob.add_mood_event("pain", /datum/mood_event/light_pain) + if(ADVMED_PAIN_THRESHOLD_MAX / 2 to ADVMED_PAIN_THRESHOLD_MAX) + assigned_mob.add_mood_event("pain", /datum/mood_event/moderate_pain) + if(ADVMED_PAIN_THRESHOLD_MAX to ADVMED_PAIN_THRESHOLD_MAX * 2) + assigned_mob.add_mood_event("pain", /datum/mood_event/severe_pain) + if(ADVMED_PAIN_THRESHOLD_MAX * 2 to ADVMED_PAIN_THRESHOLD_MAX * 4) + assigned_mob.add_mood_event("pain", /datum/mood_event/critical_pain) + if(ADVMED_PAIN_THRESHOLD_MAX * 4 to INFINITY) + assigned_mob.add_mood_event("pain", /datum/mood_event/psych_pain) + diff --git a/modular_bandastation/medical/code/additional_med_defines.dm b/modular_bandastation/medical/code/additional_med_defines.dm new file mode 100644 index 0000000000000..68bce0b0adf5d --- /dev/null +++ b/modular_bandastation/medical/code/additional_med_defines.dm @@ -0,0 +1,8 @@ +#define COMSIG_MOB_ADDMED_NECROINVERT 1 +#define WOUND_SERIES_INTERNAL_BLEED "wound_series_internal_bleed" +#define WOUND_SERIES_NECROSIS "wound_series_necrosis" + +#define TRAIT_PROFESSIONAL_DOCTOR "professional_doctor" +#define TRAIT_PROFESSIONAL_SURGEON "professional_surgeon" +#define TRAIT_SURGEON_SKILL "surgeon_skill" + diff --git a/modular_bandastation/medical/code/additional_med_functions.dm b/modular_bandastation/medical/code/additional_med_functions.dm new file mode 100644 index 0000000000000..17ed3b47407ed --- /dev/null +++ b/modular_bandastation/medical/code/additional_med_functions.dm @@ -0,0 +1,473 @@ +/mob/living/carbon/human/examine(mob/user) +//this is very slightly better than it was because you can use it more places. still can't do \his[src] though. + var/t_He = p_They() + var/t_His = p_Their() + var/t_his = p_their() + var/t_him = p_them() + var/t_has = p_have() + var/t_is = p_are() + var/obscure_name + var/obscure_examine + + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_PROSOPAGNOSIA) || HAS_TRAIT(L, TRAIT_INVISIBLE_MAN)) + obscure_name = TRUE + if(HAS_TRAIT(src, TRAIT_UNKNOWN)) + obscure_name = TRUE + obscure_examine = TRUE + + . = list("This is [!obscure_name ? name : "Unknown"]!") + + if(obscure_examine) + return list("You're struggling to make out any details...") + + var/obscured = check_obscured_slots() + + //uniform + if(w_uniform && !(obscured & ITEM_SLOT_ICLOTHING) && !(w_uniform.item_flags & EXAMINE_SKIP)) + //accessory + var/accessory_message = "" + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/undershirt = w_uniform + var/list/accessories = undershirt.list_accessories_with_icon(user) + if(length(accessories)) + accessory_message = " with [english_list(accessories)] attached" + + . += "[t_He] [t_is] wearing [w_uniform.get_examine_string(user)][accessory_message]." + //head + if(head && !(obscured & ITEM_SLOT_HEAD) && !(head.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] wearing [head.get_examine_string(user)] on [t_his] head." + //suit/armor + if(wear_suit && !(wear_suit.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] wearing [wear_suit.get_examine_string(user)]." + //suit/armor storage + if(s_store && !(obscured & ITEM_SLOT_SUITSTORE) && !(s_store.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] carrying [s_store.get_examine_string(user)] on [t_his] [wear_suit.name]." + //back + if(back && !(back.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [back.get_examine_string(user)] on [t_his] back." + + //Hands + for(var/obj/item/held_thing in held_items) + if(held_thing.item_flags & (ABSTRACT|EXAMINE_SKIP|HAND_ITEM)) + continue + . += "[t_He] [t_is] holding [held_thing.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(held_thing))]." + + //gloves + if(gloves && !(obscured & ITEM_SLOT_GLOVES) && !(gloves.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [gloves.get_examine_string(user)] on [t_his] hands." + else if(GET_ATOM_BLOOD_DNA_LENGTH(src)) + if(num_hands) + . += span_warning("[t_He] [t_has] [num_hands > 1 ? "" : "a "]blood-stained hand[num_hands > 1 ? "s" : ""]!") + + //handcuffed? + if(handcuffed) + if(istype(handcuffed, /obj/item/restraints/handcuffs/cable)) + . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] restrained with cable!") + else + . += span_warning("[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!") + + //belt + if(belt && !(belt.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [belt.get_examine_string(user)] about [t_his] waist." + + //shoes + if(shoes && !(obscured & ITEM_SLOT_FEET) && !(shoes.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] wearing [shoes.get_examine_string(user)] on [t_his] feet." + + //mask + if(wear_mask && !(obscured & ITEM_SLOT_MASK) && !(wear_mask.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [wear_mask.get_examine_string(user)] on [t_his] face." + + if(wear_neck && !(obscured & ITEM_SLOT_NECK) && !(wear_neck.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck." + + //eyes + if(!(obscured & ITEM_SLOT_EYES) ) + if(glasses && !(glasses.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [glasses.get_examine_string(user)] covering [t_his] eyes." + else if(HAS_TRAIT(src, TRAIT_UNNATURAL_RED_GLOWY_EYES)) + . += "[t_His] eyes are glowing with an unnatural red aura!" + else if(HAS_TRAIT(src, TRAIT_BLOODSHOT_EYES)) + . += "[t_His] eyes are bloodshot!" + + //ears + if(ears && !(obscured & ITEM_SLOT_EARS) && !(ears.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_has] [ears.get_examine_string(user)] on [t_his] ears." + + //ID + if(wear_id && !(wear_id.item_flags & EXAMINE_SKIP)) + . += "[t_He] [t_is] wearing [wear_id.get_examine_string(user)]." + + . += wear_id.get_id_examine_strings(user) + + //Status effects + var/list/status_examines = get_status_effect_examinations() + if (length(status_examines)) + . += status_examines + + var/appears_dead = FALSE + var/just_sleeping = FALSE + + if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH))) + appears_dead = TRUE + + var/obj/item/clothing/glasses/G = get_item_by_slot(ITEM_SLOT_EYES) + var/are_we_in_weekend_at_bernies = G?.tint && buckled && istype(buckled, /obj/vehicle/ridden/wheelchair) + + if(isliving(user) && (HAS_MIND_TRAIT(user, TRAIT_NAIVE) || are_we_in_weekend_at_bernies)) + just_sleeping = TRUE + + if(!just_sleeping) + if(HAS_TRAIT(src, TRAIT_SUICIDED)) + . += span_warning("[t_He] appear[p_s()] to have committed suicide... there is no hope of recovery.") + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + . += span_deadsay("[t_He] appear[p_s()] to be dead... but you still feel it some kind of alive.") + else + . += generate_death_examine_text() + + if(get_bodypart(BODY_ZONE_HEAD) && !get_organ_by_type(/obj/item/organ/internal/brain)) + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + . += span_deadsay("It appears that [t_his] brain is missing...") + + var/list/msg = list() + + var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/list/disabled = list() + for(var/X in bodyparts) + var/obj/item/bodypart/body_part = X + if(body_part.bodypart_disabled) + disabled += body_part + missing -= body_part.body_zone + for(var/obj/item/I in body_part.embedded_objects) + if(I.is_embed_harmless()) + msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] stuck to [t_his] [body_part.name]!\n" + else + msg += "[t_He] [t_has] [icon2html(I, user)] \a [I] embedded in [t_his] [body_part.name]!\n" + + for(var/i in body_part.wounds) + var/datum/wound/iter_wound = i + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + msg += "[iter_wound.get_examine_description(user)]\n" + else + msg += "It appears that [t_his] [iter_wound.limb] is in not normal condition\n" + + + for(var/X in disabled) + var/obj/item/bodypart/body_part = X + var/damage_text + if(HAS_TRAIT(body_part, TRAIT_DISABLED_BY_WOUND)) + continue // skip if it's disabled by a wound (cuz we'll be able to see the bone sticking out!) + if(!(body_part.get_damage() >= body_part.max_damage)) //we don't care if it's stamcritted + damage_text = "limp and lifeless" + else + damage_text = (body_part.brute_dam >= body_part.burn_dam) ? body_part.heavy_brute_msg : body_part.heavy_burn_msg + msg += "[capitalize(t_his)] [body_part.name] is [damage_text]!\n" + + //stores missing limbs + var/l_limbs_missing = 0 + var/r_limbs_missing = 0 + for(var/t in missing) + if(t == BODY_ZONE_HEAD) + msg += "[t_His] [parse_zone(t)] is missing!\n" + continue + if(t == BODY_ZONE_L_ARM || t == BODY_ZONE_L_LEG) + l_limbs_missing++ + else if(t == BODY_ZONE_R_ARM || t == BODY_ZONE_R_LEG) + r_limbs_missing++ + + msg += "[capitalize(t_his)] [parse_zone(t)] is missing!\n" + + if(l_limbs_missing >= 2 && r_limbs_missing == 0) + msg += "[t_He] look[p_s()] all right now.\n" + else if(l_limbs_missing == 0 && r_limbs_missing >= 2) + msg += "[t_He] really keep[p_s()] to the left.\n" + else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) + msg += "[t_He] [p_do()]n't seem all there.\n" + + if(!(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy))) //fake healthy + var/temp + if(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit))//fake damage + temp = 50 + else + temp = getBruteLoss() + + + var/list/damage_desc = get_majority_bodypart_damage_desc() + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + if(temp) + if(temp < 25) + msg += "[t_He] [t_has] minor [damage_desc[BRUTE]].\n" + else if(temp < 50) + msg += "[t_He] [t_has] moderate [damage_desc[BRUTE]]!\n" + else + msg += "[t_He] [t_has] severe [damage_desc[BRUTE]]!\n" + else + if(temp) + if(temp > 0) + msg += "[t_He] [t_has] [damage_desc[BRUTE]]!\n" + + temp = getFireLoss() + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + if(temp) + if(temp < 25) + msg += "[t_He] [t_has] minor [damage_desc[BURN]].\n" + else if (temp < 50) + msg += "[t_He] [t_has] moderate [damage_desc[BURN]]!\n" + else + msg += "[t_He] [t_has] severe [damage_desc[BURN]]!\n" + else + if(temp) + if(temp > 0) + msg += "[t_He] [t_has] [damage_desc[BURN]]!\n" + + if(has_status_effect(/datum/status_effect/fire_handler/fire_stacks)) + msg += "[t_He] [t_is] covered in something flammable.\n" + if(has_status_effect(/datum/status_effect/fire_handler/wet_stacks)) + msg += "[t_He] look[p_s()] a little soaked.\n" + + + if(pulledby?.grab_state) + msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" + + if(nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[t_He] [t_is] severely malnourished.\n" + else if(nutrition >= NUTRITION_LEVEL_FAT) + if(user.nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[t_He] [t_is] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" + else + msg += "[t_He] [t_is] quite chubby.\n" + switch(disgust) + if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS) + msg += "[t_He] look[p_s()] a bit grossed out.\n" + if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED) + msg += "[t_He] look[p_s()] really grossed out.\n" + if(DISGUST_LEVEL_DISGUSTED to INFINITY) + msg += "[t_He] look[p_s()] extremely disgusted.\n" + + var/apparent_blood_volume = blood_volume + if(HAS_TRAIT(src, TRAIT_USES_SKINTONES) && (skin_tone == "albino")) + apparent_blood_volume -= 150 // enough to knock you down one tier + switch(apparent_blood_volume) + if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE) + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + msg += "[t_He] [t_has] pale skin.\n" + else + msg += "[t_He] look[p_s()] like pretty pale than normal.\n" + + if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY) + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + msg += "[t_He] look[p_s()] like pale death.\n" + else + msg += "[t_He] look[p_s()] like pretty pale than normal.\n" + if(-INFINITY to BLOOD_VOLUME_BAD) + msg += "[span_deadsay("[t_He] resemble[p_s()] a crushed, empty juice pouch.")]\n" + + if(is_bleeding()) + var/list/obj/item/bodypart/bleeding_limbs = list() + var/list/obj/item/bodypart/grasped_limbs = list() + + for(var/obj/item/bodypart/body_part as anything in bodyparts) + if(body_part.get_modified_bleed_rate()) + bleeding_limbs += body_part + if(body_part.grasped_by) + grasped_limbs += body_part + + var/num_bleeds = LAZYLEN(bleeding_limbs) + + var/list/bleed_text + if(appears_dead) + bleed_text = list("Blood is visible in [t_his] open") + else + bleed_text = list("[t_He] [t_is] bleeding from [t_his]") + + switch(num_bleeds) + if(1 to 2) + bleed_text += " [bleeding_limbs[1].name][num_bleeds == 2 ? " and [bleeding_limbs[2].name]" : ""]" + if(3 to INFINITY) + for(var/i in 1 to (num_bleeds - 1)) + var/obj/item/bodypart/body_part = bleeding_limbs[i] + bleed_text += " [body_part.name]," + bleed_text += " and [bleeding_limbs[num_bleeds].name]" + + if(appears_dead) + bleed_text += ", but it has pooled and is not flowing.\n" + else + if(reagents.has_reagent(/datum/reagent/toxin/heparin, needs_metabolizing = TRUE)) + bleed_text += " incredibly quickly" + + bleed_text += "!\n" + + for(var/i in grasped_limbs) + var/obj/item/bodypart/grasped_part = i + bleed_text += "[t_He] [t_is] holding [t_his] [grasped_part.name] to slow the bleeding!\n" + + msg += bleed_text.Join() + + if(reagents.has_reagent(/datum/reagent/teslium, needs_metabolizing = TRUE)) + msg += "[t_He] [t_is] emitting a gentle blue glow!\n" + + if(just_sleeping) + msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n" + + if(!appears_dead) + var/mob/living/living_user = user + if(src != user) + if(HAS_TRAIT(user, TRAIT_EMPATH)) + if (combat_mode) + msg += "[t_He] seem[p_s()] to be on guard.\n" + if (getOxyLoss() >= 10) + msg += "[t_He] seem[p_s()] winded.\n" + if (getToxLoss() >= 10) + msg += "[t_He] seem[p_s()] sickly.\n" + if(mob_mood.sanity <= SANITY_DISTURBED) + msg += "[t_He] seem[p_s()] distressed.\n" + living_user.add_mood_event("empath", /datum/mood_event/sad_empath, src) + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + if(is_blind()) + msg += "[t_He] appear[p_s()] to be staring off into space.\n" + if (HAS_TRAIT(src, TRAIT_DEAF)) + msg += "[t_He] appear[p_s()] to not be responding to noises.\n" + if (bodytemperature > dna.species.bodytemp_heat_damage_limit) + msg += "[t_He] [t_is] flushed and wheezing.\n" + if (bodytemperature < dna.species.bodytemp_cold_damage_limit) + msg += "[t_He] [t_is] shivering.\n" + + msg += "" + + if(HAS_TRAIT(user, TRAIT_SPIRITUAL) && mind?.holy_role) + msg += "[t_He] [t_has] a holy aura about [t_him].\n" + living_user.add_mood_event("religious_comfort", /datum/mood_event/religiously_comforted) + + switch(stat) + if(UNCONSCIOUS, HARD_CRIT) + if(HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + if(stat == HARD_CRIT) + msg += "[t_He] [t_is] near death.\n" + if(stat == UNCONSCIOUS) + msg += "[t_He] [t_is] looking like sleeping.\n" + else + msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n" + if(SOFT_CRIT) + msg += "[t_He] [t_is] barely conscious.\n" + if(CONSCIOUS) + if(HAS_TRAIT(src, TRAIT_DUMB)) + msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n" + if(get_organ_by_type(/obj/item/organ/internal/brain) && isnull(ai_controller)) + if(!key) + msg += "[span_deadsay("[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.")]\n" + else if(!client) + msg += "[span_deadsay("[t_He] [t_has] a blank, absent-minded stare and appears completely unresponsive to anything. [t_He] may snap out of it soon.")]\n" + + var/scar_severity = 0 + for(var/i in all_scars) + var/datum/scar/S = i + if(S.is_visible(user)) + scar_severity += S.severity + + switch(scar_severity) + if(1 to 4) + msg += "[span_tinynoticeital("[t_He] [t_has] visible scarring, you can look again to take a closer look...")]\n" + if(5 to 8) + msg += "[span_smallnoticeital("[t_He] [t_has] several bad scars, you can look again to take a closer look...")]\n" + if(9 to 11) + msg += "[span_notice("[t_He] [t_has] significantly disfiguring scarring, you can look again to take a closer look...")]\n" + if(12 to INFINITY) + msg += "[span_notice("[t_He] [t_is] just absolutely fucked up, you can look again to take a closer look...")]\n" + msg += "" // closes info class + + if (length(msg)) + . += span_warning("[msg.Join("")]") + + var/trait_exam = common_trait_examine() + if (!isnull(trait_exam)) + . += trait_exam + + if(isliving(user)) + var/mob/living/privacy_invader = user + if(HAS_MIND_TRAIT(privacy_invader, TRAIT_MORBID)) + if(HAS_TRAIT(src, TRAIT_DISSECTED)) + msg += "[span_notice("[t_He] appears to have been dissected. Useless for examination... for now.")]\n" + if(HAS_TRAIT(src, TRAIT_SURGICALLY_ANALYZED)) + msg += "[span_notice("A skilled hand has mapped this one's internal intricacies. It will be far easier to perform future experimentations upon [t_him]. Exquisite.")]\n" + if(HAS_MIND_TRAIT(privacy_invader, TRAIT_EXAMINE_FITNESS)) + . += compare_fitness(user) + + var/organ_list = get_organs_for_zone(user.zone_selected) + if(user.grab_state == GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR)) + for (var/obj/item/organ/internal/i_organ in organ_list) + if (src.get_organ_loss(i_organ) >= 5) + msg += "[span_notice("[t_He] appears have a little mailfunction in [i_organ.name] for now.")]\n" + if (src.get_organ_loss(i_organ) >= 60) + msg += "[span_notice("[t_He] appears have a bad condition of [i_organ.name] for now.")]\n" + else if (src.get_organ_loss(i_organ) >= 100) + msg += "[span_notice("[t_He] appears have non-functional [i_organ.name] for now.")]\n" + + + if(user.grab_state == GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR) && user.zone_selected == BODY_ZONE_CHEST) + if (getToxLoss() > 10) + msg += "[span_notice("[t_He] appears have a little big liver. Maybe he have some toxins? for now.")]\n" + else if (getToxLoss() > 33) + msg += "[span_notice("[t_He] livers is little bigger than usual, is can be modarate toxin intoxication for now.")]\n" + else if (getToxLoss() > 66) + msg += "[span_notice("[t_He] livers is noticable bigger than usual, is can be severe toxin intoxication for now.")]\n" + else if (getToxLoss() > 100) + msg += "[span_notice("[t_He] livers is too big than usual, is can be critical toxin intoxication for now.")]\n" + if(user.grab_state == GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PROFESSIONAL_DOCTOR) && user.zone_selected == BODY_ZONE_PRECISE_GROIN) + if (getOxyLoss() > 25) + msg += "[span_notice("[t_He] appears have a little unstable breath for now.")]\n" + else if (getOxyLoss() > 60) + msg += "[span_notice("[t_He] appears have noticable unstable breathfor now.")]\n" + else if (getOxyLoss() > 100) + msg += "[span_notice("[t_He] appears have very unstable breathfor now.")]\n" + + var/perpname = get_face_name(get_id_name("")) + if(perpname && (HAS_TRAIT(user, TRAIT_SECURITY_HUD) || HAS_TRAIT(user, TRAIT_MEDICAL_HUD))) + var/datum/record/crew/target_record = find_record(perpname) + if(target_record) + . += "Rank: [target_record.rank]\n\[Front photo\]\[Side photo\]" + if(HAS_TRAIT(user, TRAIT_MEDICAL_HUD)) + var/cyberimp_detect + for(var/obj/item/organ/internal/cyberimp/cyberimp in organs) + if(IS_ROBOTIC_ORGAN(cyberimp) && !(cyberimp.organ_flags & ORGAN_HIDDEN)) + cyberimp_detect += "[!cyberimp_detect ? "[cyberimp.get_examine_string(user)]" : ", [cyberimp.get_examine_string(user)]"]" + if(cyberimp_detect) + . += "Detected cybernetic modifications:" + . += "[cyberimp_detect]" + if(target_record) + var/health_record = target_record.physical_status + . += "\[[health_record]\]" + health_record = target_record.mental_status + . += "\[[health_record]\]" + target_record = find_record(perpname) + if(target_record) + . += "\[Medical evaluation\]
" + . += "\[See quirks\]" + + if(HAS_TRAIT(user, TRAIT_SECURITY_HUD)) + if((user.stat == CONSCIOUS || isobserver(user)) && user != src) + //|| !user.canmove || user.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. + var/wanted_status = WANTED_NONE + var/security_note = "None." + + target_record = find_record(perpname) + if(target_record) + wanted_status = target_record.wanted_status + if(target_record.security_note) + security_note = target_record.security_note + if(ishuman(user)) + . += "Criminal status: \[[wanted_status]\]" + else + . += "Criminal status: [wanted_status]" + . += "Important Notes: [security_note]" + . += "Security record: \[View\]" + if(ishuman(user)) + . += jointext(list("\[Add citation\]", + "\[Add crime\]", + "\[Add note\]"), "") + if(isobserver(user)) + . += span_info("\nQuirks: [get_quirk_string(FALSE, CAT_QUIRK_ALL)]") + . += "
" + + SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .) diff --git a/modular_bandastation/medical/code/additional_med_mob.dm b/modular_bandastation/medical/code/additional_med_mob.dm new file mode 100644 index 0000000000000..73499c792a83a --- /dev/null +++ b/modular_bandastation/medical/code/additional_med_mob.dm @@ -0,0 +1,6 @@ +/mob/proc/add_additional_med_component() + return + +/mob/living/carbon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/additional_med, src) diff --git a/modular_bandastation/medical/code/additional_med_states.dm b/modular_bandastation/medical/code/additional_med_states.dm new file mode 100644 index 0000000000000..58cd7ac160e71 --- /dev/null +++ b/modular_bandastation/medical/code/additional_med_states.dm @@ -0,0 +1,82 @@ +/datum/status_effect/cpred + id = "cpred" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 15 + +/datum/status_effect/autocpred + id = "autocpred" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 25 + +/datum/status_effect/painkiller/low + id = "lowpk" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 50 + +/datum/status_effect/painkiller/medium + id = "midpk" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 50 + +/datum/status_effect/painkiller/high + id = "hihgpk" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 50 + +/datum/status_effect/necroinversite + id = "necroinversite_reagent" + status_type = STATUS_EFFECT_UNIQUE + alert_type = null + duration = 50 + + +// internal bleeding +/datum/status_effect/wound/internal_bleed/moderate + id = "light_ib" +/datum/status_effect/wound/internal_bleed/severe + id = "medium_ib" +/datum/status_effect/wound/internal_bleed/critical + id = "heavy_ib" + +// necrosis +/datum/status_effect/wound/necrosis/moderate + id = "light_necrosis" +/datum/status_effect/wound/necrosis/severe + id = "medium_necrosis" +/datum/status_effect/wound/necrosis/critical + id = "heavy_necrosis" + +/datum/movespeed_modifier/status_effect/pain/low + multiplicative_slowdown = 0.15 + +/datum/movespeed_modifier/status_effect/pain/midlow + multiplicative_slowdown = 0.25 + +/datum/movespeed_modifier/status_effect/pain/mid + multiplicative_slowdown = 0.45 + +/datum/movespeed_modifier/status_effect/pain/midhigh + multiplicative_slowdown = 0.85 + +/datum/movespeed_modifier/status_effect/pain/high + multiplicative_slowdown = 0.99 + +/datum/actionspeed_modifier/status_effect/pain/low + multiplicative_slowdown = 0.5 + +/datum/actionspeed_modifier/status_effect/pain/midlow + multiplicative_slowdown = 1 + +/datum/actionspeed_modifier/status_effect/pain/mid + multiplicative_slowdown = 2 + +/datum/actionspeed_modifier/status_effect/pain/midhigh + multiplicative_slowdown = 3.5 + +/datum/actionspeed_modifier/status_effect/pain/high + multiplicative_slowdown = 5 diff --git a/modular_bandastation/medical/code/cpr_extension/autocompressor.dm b/modular_bandastation/medical/code/cpr_extension/autocompressor.dm new file mode 100644 index 0000000000000..d3b87a7a695a8 --- /dev/null +++ b/modular_bandastation/medical/code/cpr_extension/autocompressor.dm @@ -0,0 +1,38 @@ +// Определяем новый объект автокомпрессора +/obj/item/clothing/suit/autocompressor + name = "auto compressor" + desc = "A suit equipped with an auto compressor to perform automatic chest compressions during CPR." + icon = 'modular_bandastation/medical/icons/medical_suits.dmi' + worn_icon = 'modular_bandastation/medical/icons/medical_suits.dmi' + icon_state = "pumper" + inhand_icon_state = "pumper" + var/mob/living/carbon/human/target + var/active = FALSE + +/obj/item/clothing/suit/autocompressor/equipped(mob/living/user, slot) + . = ..() + if (!istype(user)) + return + + if(slot & slot_flags) // Проверка, что предмет экипирован в слот, соответствующий slot_flags + target = user + spawn do_compression_loop() + +// Процедура для выполнения цикла компрессий +/obj/item/clothing/suit/autocompressor/proc/do_compression_loop() + + do + var/datum/gas_mixture/exposed_air = return_air() + + if (HAS_TRAIT(target, TRAIT_NOBREATH)) + to_chat(target, span_unconscious("You feel a mechanical force performing compressions...")) + else if (!target.get_organ_slot(ORGAN_SLOT_LUNGS)) + to_chat(target, span_unconscious("You feel a mechanical force performing compressions... but you don't feel any better...")) + else + target.apply_status_effect(/datum/status_effect/autocpred) + if (target.health <= target.crit_threshold && exposed_air && !(target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))) + target.adjustOxyLoss(-min(target.getOxyLoss(), 7)) + to_chat(target, span_unconscious("You feel a mechanical force performing compressions... It feels good...")) + + sleep (30) + while (istype(target)) diff --git a/modular_bandastation/medical/code/cpr_extension/cpr_extension.dm b/modular_bandastation/medical/code/cpr_extension/cpr_extension.dm new file mode 100644 index 0000000000000..c1cc57443cfb6 --- /dev/null +++ b/modular_bandastation/medical/code/cpr_extension/cpr_extension.dm @@ -0,0 +1,71 @@ +#define CPR_PANIC_SPEED (2 SECONDS) + +/// Убрана проверка на смерть, идет посыл сигнала проведение СЛР +/mob/living/carbon/human/do_cpr(mob/living/carbon/target) + if(target == src) + return + + var/panicking = FALSE + + do + CHECK_DNA_AND_SPECIES(target) + + if (DOING_INTERACTION_WITH_TARGET(src,target)) + return FALSE + + if (is_mouth_covered()) + balloon_alert(src, "remove your mask first!") + return FALSE + + if (target.is_mouth_covered()) + balloon_alert(src, "remove [target.p_their()] mask first!") + return FALSE + + if(HAS_TRAIT_FROM(src, TRAIT_NOBREATH, DISEASE_TRAIT)) + to_chat(src, span_warning("you can't breathe!")) + return FALSE + + var/obj/item/organ/internal/lungs/human_lungs = get_organ_slot(ORGAN_SLOT_LUNGS) + if(isnull(human_lungs)) + balloon_alert(src, "you don't have lungs!") + return FALSE + if(human_lungs.organ_flags & ORGAN_FAILING) + balloon_alert(src, "your lungs are too damaged!") + return FALSE + + visible_message(span_notice("[src] is trying to perform CPR on [target.name]!"), \ + span_notice("You try to perform CPR on [target.name]... Hold still!")) + + if (!do_after(src, delay = panicking ? CPR_PANIC_SPEED : (3 SECONDS), target = target)) + balloon_alert(src, "you fail to perform CPR!") + return FALSE + + if (target.health > target.crit_threshold) + return FALSE + + visible_message(span_notice("[src] performs CPR on [target.name]!"), span_notice("You perform CPR on [target.name].")) + if(HAS_MIND_TRAIT(src, TRAIT_MORBID)) + add_mood_event("morbid_saved_life", /datum/mood_event/morbid_saved_life) + else + add_mood_event("saved_life", /datum/mood_event/saved_life) + log_combat(src, target, "CPRed") + + 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 + target.apply_status_effect(/datum/status_effect/cpred) + if (!(target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))) + target.adjustOxyLoss(-min(target.getOxyLoss(), 7)) + to_chat(target, span_unconscious("You feel a breath of fresh air enter your lungs... It feels good...")) + + if (target.health <= target.crit_threshold) + if (!panicking) + to_chat(src, span_warning("[target] still isn't up! You try harder!")) + panicking = TRUE + else + panicking = FALSE + + while (panicking) +#undef CPR_PANIC_SPEED diff --git a/modular_bandastation/medical/code/machines/surgery_table_v1.dm b/modular_bandastation/medical/code/machines/surgery_table_v1.dm new file mode 100644 index 0000000000000..dd3a3b8cd9775 --- /dev/null +++ b/modular_bandastation/medical/code/machines/surgery_table_v1.dm @@ -0,0 +1,181 @@ +/obj/machinery/optable/ + smoothing_flags = NONE + smoothing_groups = null + canSmoothWith = null + can_buckle = TRUE + buckle_lying = 90 + density = TRUE + anchored = TRUE + pass_flags_self = PASSTABLE | LETPASSTHROW + layer = TABLE_LAYER + obj_flags = CAN_BE_HIT | IGNORE_DENSITY + var/mob/living/carbon/patient = null + var/obj/machinery/computer/operating/computer = null + +/obj/machinery/optable/proc/table_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) + SIGNAL_HANDLER + if((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)) + return + target.Knockdown(SHOVE_KNOCKDOWN_TABLE) + target.visible_message(span_danger("[shover.name] shoves [target.name] onto \the [src]!"), + span_userdanger("You're shoved onto \the [src] by [shover.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) + to_chat(shover, span_danger("You shove [target.name] onto \the [src]!")) + target.throw_at(src, 1, 1, null, FALSE) //1 speed throws with no spin are basically just forcemoves with a hard collision check + log_combat(shover, target, "shoved", "onto [src] (table)[weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED + +/obj/machinery/optable/Initialize(mapload, _buildstack) + . = ..() + AddElement(/datum/element/footstep_override, priority = STEP_SOUND_TABLE_PRIORITY) + + make_climbable() + + var/static/list/loc_connections = list( + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(table_living), + ) + + AddElement(/datum/element/connect_loc, loc_connections) + var/static/list/give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN, TRAIT_TURF_IGNORE_SLIPPERY, TRAIT_IMMERSE_STOPPED) + AddElement(/datum/element/give_turf_traits, give_turf_traits) + register_context() + +/obj/machinery/optable/attack_hand(mob/living/user, list/modifiers) + if(Adjacent(user) && user.pulling) + if(isliving(user.pulling)) + var/mob/living/pushed_mob = user.pulling + if(pushed_mob.buckled) + if(pushed_mob.buckled == src) + //Already buckled to the table, you probably meant to unbuckle them + return ..() + to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) + return + if(user.combat_mode) + switch(user.grab_state) + if(GRAB_PASSIVE) + to_chat(user, span_warning("You need a better grip to do that!")) + return + if(GRAB_AGGRESSIVE) + tablepush(user, pushed_mob) + if(GRAB_NECK to GRAB_KILL) + tablelimbsmash(user, pushed_mob) + else + pushed_mob.visible_message(span_notice("[user] begins to place [pushed_mob] onto [src]..."), \ + span_userdanger("[user] begins to place [pushed_mob] onto [src]...")) + if(do_after(user, 3.5 SECONDS, target = pushed_mob)) + tableplace(user, pushed_mob) + else + return + user.stop_pulling() + else if(user.pulling.pass_flags & PASSTABLE) + user.Move_Pulled(src) + if (user.pulling.loc == loc) + user.visible_message(span_notice("[user] places [user.pulling] onto [src]."), + span_notice("You place [user.pulling] onto [src].")) + user.stop_pulling() + return ..() + +/obj/machinery/optable/proc/make_climbable() + AddElement(/datum/element/climbable) + AddElement(/datum/element/elevation, pixel_shift = 12) + +/obj/machinery/optable/proc/tableplace(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(loc) + pushed_mob.set_resting(TRUE, TRUE) + pushed_mob.visible_message(span_notice("[user] places [pushed_mob] onto [src]."), \ + span_notice("[user] places [pushed_mob] onto [src].")) + log_combat(user, pushed_mob, "places", null, "onto [src]") + +/obj/machinery/optable/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob) + pushed_mob.Knockdown(30) + var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD) + var/extra_wound = 0 + if(HAS_TRAIT(user, TRAIT_HULK)) + extra_wound = 20 + banged_limb?.receive_damage(30, wound_bonus = extra_wound) + pushed_mob.apply_damage(60, STAMINA) + take_damage(50) + if(user.mind?.martial_art?.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE) + pushed_mob.visible_message(span_danger("[user] smashes [pushed_mob]'s [banged_limb.plaintext_zone] against \the [src]!"), + span_userdanger("[user] smashes your [banged_limb.plaintext_zone] against \the [src]")) + log_combat(user, pushed_mob, "head slammed", null, "against [src]") + pushed_mob.add_mood_event("table", /datum/mood_event/table_limbsmash, banged_limb) + +/obj/machinery/optable/proc/tablepush(mob/living/user, mob/living/pushed_mob) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, span_danger("Throwing [pushed_mob] onto the table might hurt them!")) + return + var/passtable_key = REF(user) + passtable_on(pushed_mob, passtable_key) + for (var/obj/obj in user.loc.contents) + if(!obj.CanAllowThrough(pushed_mob)) + return + pushed_mob.Move(src.loc) + passtable_off(pushed_mob, passtable_key) + if(pushed_mob.loc != loc) //Something prevented the tabling + return + pushed_mob.Knockdown(30) + pushed_mob.apply_damage(10, BRUTE) + pushed_mob.apply_damage(40, STAMINA) + if(user.mind?.martial_art?.smashes_tables && user.mind?.martial_art.can_use(user)) + deconstruct(FALSE) + playsound(pushed_mob, 'sound/effects/tableslam.ogg', 90, TRUE) + pushed_mob.visible_message(span_danger("[user] slams [pushed_mob] onto \the [src]!"), \ + span_userdanger("[user] slams you onto \the [src]!")) + log_combat(user, pushed_mob, "tabled", null, "onto [src]") + pushed_mob.add_mood_event("table", /datum/mood_event/table) + +///Align the mob with the table when buckled. +/obj/machinery/optable/post_buckle_mob(mob/living/buckled) + . = ..() + buckled.pixel_y += 6 + +///Disalign the mob with the table when unbuckled. +/obj/machinery/optable/post_unbuckle_mob(mob/living/buckled) + . = ..() + buckled.pixel_y -= 6 + +/// Any mob that enters our tile will be marked as a potential patient. They will be turned into a patient if they lie down. +/obj/machinery/optable/proc/mark_patient(datum/source, mob/living/carbon/potential_patient) + SIGNAL_HANDLER + if(!istype(potential_patient)) + return + RegisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(recheck_patient)) + recheck_patient(potential_patient) // In case the mob is already lying down before they entered. + +/// Unmark the potential patient. +/obj/machinery/optable/proc/unmark_patient(datum/source, mob/living/carbon/potential_patient) + SIGNAL_HANDLER + if(!istype(potential_patient)) + return + if(potential_patient == patient) + recheck_patient(patient) // Can just set patient to null, but doing the recheck lets us find a replacement patient. + UnregisterSignal(potential_patient, COMSIG_LIVING_SET_BODY_POSITION) + +/// Someone on our tile just lied down, got up, moved in, or moved out. +/// potential_patient is the mob that had one of those four things change. +/// The check is a bit broad so we can find a replacement patient. +/obj/machinery/optable/proc/recheck_patient(mob/living/carbon/potential_patient) + SIGNAL_HANDLER + if(patient && patient != potential_patient) + return + + if(potential_patient.body_position == LYING_DOWN && potential_patient.loc == loc) + patient = potential_patient + return + + // Find another lying mob as a replacement. + for (var/mob/living/carbon/replacement_patient in loc.contents) + if(replacement_patient.body_position == LYING_DOWN) + patient = replacement_patient + return + patient = null + +/obj/machinery/optable/Destroy() + if(computer && computer.table == src) + computer.table = null + patient = null + UnregisterSignal(loc, COMSIG_ATOM_ENTERED) + UnregisterSignal(loc, COMSIG_ATOM_EXITED) + return ..() diff --git a/modular_bandastation/medical/code/machines/surgery_table_v2.dm b/modular_bandastation/medical/code/machines/surgery_table_v2.dm new file mode 100644 index 0000000000000..b1879f5f2d194 --- /dev/null +++ b/modular_bandastation/medical/code/machines/surgery_table_v2.dm @@ -0,0 +1,208 @@ +/obj/machinery/optable/v2 + name = "advanced operating table" + desc = "Used for advanced medical procedures. This one have build in mask and slot for anestetic tanks" + icon = 'modular_bandastation/medical/icons/surgery_table.dmi' + icon_state = "optable" + var/obj/item/tank/internals/tank = null + var/obj/item/clothing/mask/mask = null + var/current_breathing = FALSE + +/obj/machinery/optable/v2/Initialize(mapload) + . = ..() + for(var/direction in GLOB.alldirs) + computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) + if(computer) + computer.table = src + break + + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(mark_patient)) + RegisterSignal(loc, COMSIG_ATOM_EXITED, PROC_REF(unmark_patient)) + START_PROCESSING(SSobj, src) + +/obj/machinery/optable/v2/examine(mob/user) + . = ..() + . += "
" + + if(tank) + . += span_info("There is a [tank] on side.") + else + . += span_warning("There is an empty place for tank.") + + if(mask) + . += span_info("There is a [mask].") + else + . += span_warning("There is an empty place for mask") + +/obj/machinery/optable/v2/examine_more(mob/user) + . = ..() + . += span_notice("You can remove tank by pressing Alt.") + if(tank && mask) . += span_info("
You can turn on anestesia when someone lying on it.") + +/obj/machinery/optable/v2/attack_hand(mob/user, act_intent, attackchain_flags) + recheck_patient(patient) + if (isnull(patient)) + return + if(tank && mask) + if(!patient.internal) + to_chat(user, span_notice("You begin to turn on anestesia.")) + if(patient.stat != UNCONSCIOUS) + to_chat(patient, span_danger("[user] begin to turn on anestesia!")) + if(!do_after(user, 5 SECONDS, patient)) + return + recheck_patient(patient) + if (isnull(patient)) + to_chat(user, span_danger("You failed to equip breath mask on [patient]!")) + return + if(patient.wear_mask) + if(isclothing(patient.wear_mask)) + var/obj/item/clothing/patient_item_in_mask_slot = patient.wear_mask + if(!patient_item_in_mask_slot.clothing_flags) + if(!patient.dropItemToGround(patient.wear_mask)) + to_chat(user, span_danger("You failed to drop mask from [patient], to equip breath mask!")) + return + else + if(!patient.dropItemToGround(patient.wear_mask)) + to_chat(user, span_danger("You failed to drop item from [patient], to equip breath mask!")) + return + patient.equip_to_slot_if_possible(mask, ITEM_SLOT_MASK) + if(!patient.wear_mask) + to_chat(user, span_danger("You failed to equip breath mask on [patient]!")) + return + patient.internal = tank + tank.loc = patient + user.visible_message("[user] switch on anestesia on [patient].", span_notice("You open a vent for anestesia.")) + patient.open_internals(patient.internal) + current_breathing = TRUE + update_overlays() + return + else + if(patient.internal != tank) + to_chat(user, span_danger("You need to turn off [patient]'s tank!")) + return + if(!do_after(user, 1 SECONDS, patient)) + return + user.visible_message("[user] switch off anestesia on [patient].", span_notice("You close a vent for anestesia.")) + stop_breath() + else + to_chat(user, span_warning("[src] don't have a tank or mask!")) + +/obj/machinery/optable/v2/process() + if (!isnull(patient)) + recheck_patient(patient) + if(mask?.loc != patient || isnull(tank) || patient?.loc != loc) + stop_breath() + +/obj/machinery/optable/v2/proc/stop_breath() + recheck_patient(patient) + if(!patient) + if(mask) + mask.forceMove(src) + return + if(mask && mask.loc != src) + visible_message(span_notice("[mask] returning to it's place.")) + patient.transferItemToLoc(mask, src, TRUE) + tank.loc = src + tank.after_internals_closed(patient) + patient.internal = null + current_breathing = FALSE + if (patient.body_position != LYING_DOWN) + patient = null + update_overlays() + +/obj/machinery/optable/v2/click_alt(mob/living/user) + if(!ishuman(user)) + to_chat(user, span_warning("it's to difficult for you!")) + return + if(patient) + to_chat(user, span_warning("Need to remove patient first!")) + return + if(tank && !patient?.internal) + to_chat(user, span_notice("You remove [tank] from side of table.")) + user.put_in_hands(tank) + tank = null + update_overlays() + + else if(mask && !patient?.internal) + to_chat(user, span_notice("You remove [mask] from side of table.")) + user.put_in_hands(mask) + mask = null + update_overlays() + return TRUE + +/obj/machinery/optable/v2/update_overlays() + SHOULD_CALL_PARENT(TRUE) + .=..() + var/static/mask_is + var/static/tank_is + var/static/bad_state + var/static/good_state + var/static/mid_state + var/static/maskwork + if(isnull(mask_is)) //static vars initialize with global variables, meaning src is null and this won't pass integration tests unless you check. + tank_is = iconstate2appearance(icon, "balon_2") + mask_is = iconstate2appearance(icon, "mask") + good_state = iconstate2appearance(icon, "over_green") + mid_state = iconstate2appearance(icon, "over_yello") + bad_state = iconstate2appearance(icon, "over_red") + maskwork = iconstate2appearance(icon, "mask_equip") + if (isnull(mask)) + cut_overlay(mask_is) + else + add_overlay(mask_is) + if (isnull(tank)) + cut_overlay(tank_is) + else + add_overlay(tank_is) + if (isnull(mask) && isnull(tank)) + add_overlay(bad_state) + cut_overlay(good_state) + cut_overlay(mid_state) + else if (isnull(mask) || isnull(tank)) + add_overlay(mid_state) + cut_overlay(good_state) + cut_overlay(bad_state) + else + add_overlay(good_state) + cut_overlay(mid_state) + cut_overlay(bad_state) + if (current_breathing) + add_overlay(maskwork) + cut_overlay(mask_is) + else + if (isnull(mask)) + cut_overlay(mask_is) + else + add_overlay(mask_is) + cut_overlay(maskwork) + +/obj/machinery/optable/v2/attacked_by(obj/item/I, mob/living/user) + if(!user.combat_mode) + if(!tank) + if(istype(I, /obj/item/tank/internals)) + if(user.transferItemToLoc(I, src)) + user.visible_message("[user] fixes [I] from side of table.", span_notice("You fix [I] from side of table.")) + tank = I + update_overlays() + return ITEM_INTERACT_SUCCESS + if(!mask) + if(istype(I, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/potential_mask = I + if(potential_mask.clothing_flags) + if(user.transferItemToLoc(I, src)) + user.visible_message("[user] fixes [I] on mask stand.", span_notice("You fix [I] on mask stand.")) + mask = I + update_overlays() + return ITEM_INTERACT_SUCCESS + else + . = ..() + +/obj/machinery/optable/v2/Destroy() + if(tank) + tank.forceMove(loc) + tank = null + if(mask) + mask.forceMove(loc) + mask = null + STOP_PROCESSING(SSobj, src) + stop_breath() + . = ..() diff --git a/modular_bandastation/medical/code/machines/surgery_table_v3.dm b/modular_bandastation/medical/code/machines/surgery_table_v3.dm new file mode 100644 index 0000000000000..b303e231196f1 --- /dev/null +++ b/modular_bandastation/medical/code/machines/surgery_table_v3.dm @@ -0,0 +1,67 @@ +/obj/machinery/optable/v3 + name = "operating table N.E.R.V." + desc = "Used for advanced medical procedures. Comes with built-in neural suppressors to anesthetize a patient laying on top of it." + icon = 'modular_bandastation/medical/icons/operation_table_v3.dmi' + icon_state = "table2-idle" + var/suppressing = FALSE + +/obj/machinery/optable/v3/Initialize(mapload) + . = ..() + for(var/direction in GLOB.alldirs) + computer = locate(/obj/machinery/computer/operating) in get_step(src, direction) + if(computer) + computer.table = src + break + + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(mark_patient)) + RegisterSignal(loc, COMSIG_ATOM_EXITED, PROC_REF(unmark_patient)) + START_PROCESSING(SSobj, src) + +/obj/machinery/optable/v3/examine(mob/user) + . = ..() + . += "
" + + . += span_info("The neural suppressors are switched [suppressing ? "on" : "off"].") + +/obj/machinery/optable/v3/attack_hand(mob/user, act_intent, attackchain_flags) + recheck_patient(patient) + if(isnull(patient)) + to_chat(user, span_danger("There is nobody on \the [src]. It would be pointless to turn the suppressor on.")) + return TRUE + + if(user != patient && !suppressing) // Skip checks if you're doing it to yourself or turning it off, this is an anti-griefing mechanic more than anything. + user.visible_message(span_danger("\The [user] begins switching on \the [src]'s neural suppressor.")) + if(!do_after(user, 2 SECONDS, patient)) + return TRUE + if(isnull(patient)) + to_chat(user, span_danger("There is nobody on \the [src]. It would be pointless to turn the suppressor on.")) + return TRUE + + suppressing = !suppressing + user.visible_message(span_notice("\The [user] switches [suppressing ? "on" : "off"] \the [src]'s neural suppressor.")) + if (patient.stat == UNCONSCIOUS) + to_chat(patient, span_notice("... [pick("good feeling", "white light", "pain fades away", "safe now")] ...")) + return TRUE + +/obj/machinery/optable/v3/process() + if (!isnull(patient)) + recheck_patient(patient) + if(patient?.loc != loc) + stop_supress() + if (suppressing) + icon_state = "table2-active" + else + icon_state = "table2-idle" + +/obj/machinery/optable/v3/proc/stop_supress() + suppressing = FALSE + +/obj/machinery/optable/v3/Destroy() + STOP_PROCESSING(SSobj, src) + stop_supress() + . = ..() + +/obj/machinery/optable/v3/recheck_patient() + . = .. () + if(patient && suppressing) + patient.apply_status_effect(/datum/status_effect/incapacitating/sleeping, 40) diff --git a/modular_bandastation/medical/code/machines/surgery_tables.dm b/modular_bandastation/medical/code/machines/surgery_tables.dm new file mode 100644 index 0000000000000..d7db3d0cc34ef --- /dev/null +++ b/modular_bandastation/medical/code/machines/surgery_tables.dm @@ -0,0 +1,19 @@ +/obj/item/circuitboard/machine/surgerytablev2 + name = "Advanced surgery table" + greyscale_colors = CIRCUIT_COLOR_MEDICAL + build_path = /obj/machinery/optable/v2 + req_components = list( + /datum/stock_part/matter_bin = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/mineral/titanium = 4) + +/obj/item/circuitboard/machine/surgerytablev3 + name = "Surgery table N.E.R.V." + greyscale_colors = CIRCUIT_COLOR_MEDICAL + build_path = /obj/machinery/optable/v3 + req_components = list( + /datum/stock_part/matter_bin = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/mineral/titanium = 3, + /obj/item/stack/sheet/mineral/gold = 1 + ) diff --git a/modular_bandastation/medical/code/medical_items/chem_bag.dm b/modular_bandastation/medical/code/medical_items/chem_bag.dm new file mode 100644 index 0000000000000..c5b27b68bd3d8 --- /dev/null +++ b/modular_bandastation/medical/code/medical_items/chem_bag.dm @@ -0,0 +1,17 @@ +/obj/item/storage/chem_bag + name = "Chemical bag" + desc = "Pretty big bag for storing chemicals before put them into fridge." + icon_state = "bag" + icon = 'icons/obj/medical/chemical.dmi' + inhand_icon_state = "contsolid" + worn_icon_state = "nothing" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + storage_type = /datum/storage/chemical_storage + + +/datum/storage/chemical_storage + max_slots = 75 + numerical_stacking = TRUE + screen_max_columns = 4 diff --git a/modular_bandastation/medical/code/medical_items/field_kit.dm b/modular_bandastation/medical/code/medical_items/field_kit.dm new file mode 100644 index 0000000000000..dc645a4d9bc85 --- /dev/null +++ b/modular_bandastation/medical/code/medical_items/field_kit.dm @@ -0,0 +1,17 @@ +/obj/item/storage/field_kit + name = "Field kit" + desc = "Small container for storing medical supplies." + icon_state = "bluekit" + icon = 'modular_bandastation/medical/icons/kits.dmi' + inhand_icon_state = "contsolid" + worn_icon_state = "nothing" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + storage_type = /datum/storage/medical_storage + + +/datum/storage/medical_storage + max_slots = 40 + numerical_stacking = TRUE + screen_max_columns = 10 diff --git a/modular_bandastation/medical/code/medical_items/iv_bags.dm b/modular_bandastation/medical/code/medical_items/iv_bags.dm new file mode 100644 index 0000000000000..43fd999e0efc1 --- /dev/null +++ b/modular_bandastation/medical/code/medical_items/iv_bags.dm @@ -0,0 +1,233 @@ +#define ALERT_IV_CONNECTED "iv_connected" +///Minimum possible IV drip transfer rate in units per second +#define MIN_IV_TRANSFER_RATE 0 +///Maximum possible IV drip transfer rate in units per second +#define MAX_IV_TRANSFER_RATE 5 +///What the transfer rate value is rounded to +#define IV_TRANSFER_RATE_STEP 0.01 +///Default IV drip transfer rate in units per second +#define DEFAULT_IV_TRANSFER_RATE 5 + +/obj/item/reagent_containers/chem_pack/click_alt(mob/living/user) + if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))) + to_chat(user, span_warning("Uh... whoops! You accidentally spill the content of the bag onto yourself.")) + SplashReagents(user) + return CLICK_ACTION_BLOCKING + + if(sealed) + reagents.flags = NONE + reagent_flags = REFILLABLE | DRAINABLE | TRANSPARENT //To allow for sabotage or ghetto use. + reagents.flags = reagent_flags + spillable = TRUE + sealed = FALSE + balloon_alert(user, "unsealed") + return CLICK_ACTION_SUCCESS + else + reagents.flags = NONE + reagent_flags = DRAWABLE | INJECTABLE //To allow for sabotage or ghetto use. + reagents.flags = reagent_flags + spillable = FALSE + sealed = TRUE + balloon_alert(user, "sealed") + return CLICK_ACTION_SUCCESS + +/obj/item/reagent_containers/chem_pack/examine() + . = ..() + if(sealed) + . += span_notice("The bag is sealed shut. Alt-click to unseal it.") + else + . += span_notice("The bag is open. Alt-click to seal it.") + +/obj/item/reagent_containers/blood + name = "blood pack" + desc = "Contains blood used for transfusion. Can be attached to an IV drip." + icon = 'icons/obj/medical/bloodpack.dmi' + icon_state = "bloodpack" + volume = 200 + var/sealed = TRUE + var/injecting = TRUE + var/injection_target + var/transfer_rate = DEFAULT_IV_TRANSFER_RATE + fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100) + +/obj/item/reagent_containers/blood/click_alt(mob/living/user) + if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) && !sealed) + to_chat(user, span_warning("Uh... whoops! You accidentally spill the content of the bag onto yourself.")) + SplashReagents(user) + return CLICK_ACTION_BLOCKING + + if(sealed) + reagents.flags = NONE + reagent_flags = REFILLABLE | DRAINABLE | TRANSPARENT //To allow for sabotage or ghetto use. + reagents.flags = reagent_flags + spillable = TRUE + sealed = FALSE + balloon_alert(user, "unsealed") + return CLICK_ACTION_SUCCESS + else + reagents.flags = NONE + reagent_flags = DRAWABLE | INJECTABLE //To allow for sabotage or ghetto use. + reagents.flags = reagent_flags + spillable = FALSE + sealed = TRUE + balloon_alert(user, "sealed") + return CLICK_ACTION_SUCCESS + +/obj/item/reagent_containers/blood/click_alt_secondary(mob/user) + . = ..() + if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) && !sealed) + to_chat(user, span_warning("Uh... whoops! You accidentally spill the content of the bag onto yourself.")) + SplashReagents(user) + return CLICK_ACTION_BLOCKING + + if(injecting) + injecting = FALSE + balloon_alert(user, "Draw") + return CLICK_ACTION_SUCCESS + else + injecting = TRUE + balloon_alert(user, "Inject") + return CLICK_ACTION_SUCCESS + +/obj/item/reagent_containers/blood/examine() + . = ..() + if(sealed) + . += span_notice("The bag is sealed shut. Alt-click to unseal it.") + else + . += span_notice("The bag is open. Alt-click to seal it.") + if(injecting) + . += span_notice("The bag is in inject mode and will transfer to patient. Alt-second-click to change it") + else + . += span_notice("The bag is in draw mode and will transfer from patient. Alt-second-click to change it") + +/obj/item/reagent_containers/blood/interact_with_atom(atom/target, mob/user, proximity) + + if(!proximity) + return + if(!target.reagents) + return + + if(isliving(target)) + var/mob/living/L = target + if(injection_target) // Removing the needle + detach_iv(user) + else // Inserting the needle + attach_iv(L,user) + + else if(target.is_refillable() && is_drainable()) // Transferring from IV bag to other containers + if(!reagents.total_volume) + to_chat(user, "[src] is empty.") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + var/trans = reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [trans] units of the solution to [target].") + +/obj/item/reagent_containers/blood/proc/get_reagents() + return src?.reagents + +/obj/item/reagent_containers/blood/proc/attach_iv(atom/target, mob/user) + if(isliving(target)) + user.visible_message(span_warning("[usr] begins attaching [src] to [target]..."), span_warning("You begin attaching [src] to [target].")) + if(!do_after(usr, 3 SECONDS, target)) + return + + var/mob/living/victim = target + if (victim.body_position != LYING_DOWN) + injecting = FALSE + + usr.visible_message(span_warning("[usr] attaches [src] to [target]."), span_notice("You attach [src] to [target].")) + var/datum/reagents/container = get_reagents() + log_combat(usr, target, "attached", src, "containing: ([container.get_reagent_log_string()])") + add_fingerprint(usr) + if(isliving(target)) + var/mob/living/target_mob = target + target_mob.throw_alert(ALERT_IV_CONNECTED, /atom/movable/screen/alert/iv_connected) + injection_target = target + START_PROCESSING(SSmachines, src) + update_appearance(UPDATE_ICON) + + SEND_SIGNAL(src, COMSIG_IV_ATTACH, target) + +/obj/item/reagent_containers/blood/proc/detach_iv(mob/user) + if(isliving(injection_target)) + user.visible_message(span_warning("[usr] begins detaching [src] to [injection_target]..."), span_warning("You begin detaching [src] to [injection_target].")) + if(!do_after(usr, 3 SECONDS, injection_target)) + return + if(injection_target) + visible_message(span_notice("[injection_target] is detached from [src].")) + if(isliving(injection_target)) + var/mob/living/attached_mob = injection_target + attached_mob.clear_alert(ALERT_IV_CONNECTED, /atom/movable/screen/alert/iv_connected) + SEND_SIGNAL(src, COMSIG_IV_DETACH, injection_target) + injection_target = null + update_appearance(UPDATE_ICON) + +/obj/item/reagent_containers/blood/proc/set_transfer_rate(new_rate) + transfer_rate = round(clamp(new_rate, MIN_IV_TRANSFER_RATE, MAX_IV_TRANSFER_RATE), IV_TRANSFER_RATE_STEP) + update_appearance(UPDATE_ICON) + +/obj/item/reagent_containers/blood/process(seconds_per_tick) + if(!injection_target) + return PROCESS_KILL + + var/mob/check_mob = recursive_loc_check(src, injection_target) + var/mob/living/target_mob = check_mob + if (target_mob.body_position != LYING_DOWN) + injecting = FALSE + + if(amount_per_transfer_from_this > 10) // Prevents people from switching to illegal transfer values while the IV is already in someone, i.e. anything over 10 + visible_message("The IV bag's needle pops out of [injection_target]'s arm. The transfer amount is too high!") + if(injection_target) + visible_message(span_notice("[injection_target] is detached from [src].")) + if(isliving(injection_target)) + var/mob/living/attached_mob = injection_target + attached_mob.clear_alert(ALERT_IV_CONNECTED, /atom/movable/screen/alert/iv_connected) + SEND_SIGNAL(src, COMSIG_IV_DETACH, injection_target) + injection_target = null + update_appearance(UPDATE_ICON) + return PROCESS_KILL + + if (istype(injection_target, /mob/living)) + var/mob/living/carbon/attached_mob = injection_target + if(!(get_dist(src, attached_mob) <= 1 && isturf(attached_mob.loc))) + if(isliving(attached_mob)) + to_chat(attached_mob, span_userdanger("The IV drip needle is ripped out of you, leaving an open bleeding wound!")) + var/list/arm_zones = shuffle(list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)) + var/obj/item/bodypart/chosen_limb = attached_mob.get_bodypart(arm_zones[1]) || attached_mob.get_bodypart(arm_zones[2]) || attached_mob.get_bodypart(BODY_ZONE_CHEST) + chosen_limb.receive_damage(3) + attached_mob.cause_wound_of_type_and_severity(WOUND_PIERCE, chosen_limb, WOUND_SEVERITY_MODERATE, wound_source = "IV needle") + else + visible_message(span_warning("[injection_target] is detached from [src].")) + detach_iv() + return PROCESS_KILL + + var/datum/reagents/drip_reagents = get_reagents() + if(!drip_reagents) + return PROCESS_KILL + + if(!transfer_rate) + return + + // Give reagents + if(injecting) + if(drip_reagents.total_volume) + drip_reagents.trans_to(injection_target, transfer_rate * seconds_per_tick, methods = INJECT, show_message = FALSE) //make reagents reacts, but don't spam messages + update_appearance(UPDATE_ICON) + + // Take blood + else if (isliving(injection_target)) + var/mob/living/attached_mob = injection_target + var/amount = min(transfer_rate * seconds_per_tick, drip_reagents.maximum_volume - drip_reagents.total_volume) + // If the beaker is full, ping + if(!amount) + set_transfer_rate(MIN_IV_TRANSFER_RATE) + audible_message(span_hear("[src] pings.")) + return + var/atom/movable/target = src?.reagents + attached_mob.transfer_blood_to(target, amount) + update_appearance(UPDATE_ICON) + diff --git a/modular_bandastation/medical/code/medical_items/medical_pen.dm b/modular_bandastation/medical/code/medical_items/medical_pen.dm new file mode 100644 index 0000000000000..43030554fab53 --- /dev/null +++ b/modular_bandastation/medical/code/medical_items/medical_pen.dm @@ -0,0 +1,84 @@ +/obj/item/flashlight/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + var/turf/user_turf = user.loc + if(!isturf(user_turf)) + return + var/light_strength = light_power + user_turf.get_lumcount() - 0.5 + + if(!ishuman(interacting_with)) + return NONE + if(!light_on) + return NONE + add_fingerprint(user) + if(user.combat_mode || (user.zone_selected != BODY_ZONE_PRECISE_EYES && user.zone_selected != BODY_ZONE_PRECISE_MOUTH)) + return NONE + if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly + return ITEM_INTERACT_SKIP_TO_ATTACK //just hit them in the head + + . = ITEM_INTERACT_BLOCKING + if(!ISADVANCEDTOOLUSER(user)) + to_chat(user, span_warning("You don't have the dexterity to do this!")) + return + var/mob/living/scanning = interacting_with + if(!scanning.get_bodypart(BODY_ZONE_HEAD)) + to_chat(user, span_warning("[scanning] doesn't have a head!")) + return + if(light_strength < 1) + to_chat(user, span_warning("[src] isn't bright enough to see anything!")) + return + + var/list/render_list = list() + switch(user.zone_selected) + if(BODY_ZONE_PRECISE_EYES) + render_list += eye_examine(scanning, user) + if(BODY_ZONE_PRECISE_MOUTH) + render_list += mouth_examine(scanning, user) + + if(length(render_list)) + //display our packaged information in an examine block for easy reading + to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/item/flashlight/eye_examine(mob/living/carbon/human/M, mob/living/user) + . = list() + if((M.head && M.head.flags_cover & HEADCOVERSEYES) || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) || (M.glasses && M.glasses.flags_cover & GLASSESCOVERSEYES)) + to_chat(user, span_warning("You're going to need to remove that [(M.head && M.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask": "glasses"] first!")) + 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) + var/obj/item/organ/internal/liver = M.get_organ_slot(ORGAN_SLOT_LIVER) + 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 + user.visible_message(span_warning("[user] shines [src] into [M.p_their()] eyes."), ignored_mobs = user) + . += span_info("You direct [src] to into your eyes:\n") + + if(M.is_blind()) + . += "You're not entirely certain what you were expecting...\n" + else + . += "Trippy!\n" + + else + user.visible_message(span_warning("[user] directs [src] to [M]'s eyes."), ignored_mobs = user) + . += span_info("You direct [src] to [M]'s eyes:\n") + + if(M.stat == DEAD || M.is_blind() || M.get_eye_protection() > FLASH_PROTECTION_WELDER) + . += "[M.p_Their()] pupils don't react to the light!\n"//mob is dead + else if(liver.damage < 20 && E.damage < 10 && liver.damage < 20) + . += "[M.p_Their()] pupils narrow.\n"//they're okay :D + if(brain.damage > 20) + . += "[M.p_Their()] pupils contract unevenly!\n"//mob has sustained damage to their brain + if(E.damage > 10) + . += "[M.p_Their()] eyes have some damaged veins!\n"//mob has sustained damage to their eyes + if(liver.damage > 20) + . += "[M.p_Their()] eyes have strange color around them!\n"//mob has sustained damage to their liver + + if(M.dna && M.dna.check_mutation(/datum/mutation/human/xray)) + . += "[M.p_Their()] pupils give an eerie glow!\n"//mob has X-ray vision + + return . diff --git a/modular_bandastation/medical/code/mood/moodlets.dm b/modular_bandastation/medical/code/mood/moodlets.dm new file mode 100644 index 0000000000000..c895ae1d78a1c --- /dev/null +++ b/modular_bandastation/medical/code/mood/moodlets.dm @@ -0,0 +1,29 @@ +/datum/mood_event/light_pain + description = "I feel a little irritated of some pickle sensations." + mood_change = -2 + timeout = 1 MINUTES + category = "pain" + +/datum/mood_event/moderate_pain + description = "I feel a little nerveous of noticable pain." + mood_change = -3 + timeout = 1 MINUTES + category = "pain" + +/datum/mood_event/severe_pain + description = "I can't focus on anything beside this pain." + mood_change = -5 + timeout = 1 MINUTES + category = "pain" + +/datum/mood_event/critical_pain + description = "I barely stay in mind of this pain" + mood_change = -10 + timeout = 1 MINUTES + category = "pain" + +/datum/mood_event/psych_pain + description = "THERE IS ONLY ONE WAY TO END THIS - JUST KILL ME ALREADY!!!" + mood_change = -100 + timeout = 1 MINUTES + category = "pain" diff --git a/modular_bandastation/medical/code/new_chemicals/organ_chemicals.dm b/modular_bandastation/medical/code/new_chemicals/organ_chemicals.dm new file mode 100644 index 0000000000000..927c9b96a5318 --- /dev/null +++ b/modular_bandastation/medical/code/new_chemicals/organ_chemicals.dm @@ -0,0 +1,71 @@ +//Серия ниже восстанавливает органы в холодной среде. Работают при -50 по цельсию (173K), и "мощность" растет до 103К (-120 по цельсию), после чего падает снова до 33 +/datum/reagent/medicine/cryodrit_base + name = "cryodrit base" + description = "you not suposed to see this" + color = "#00c8be" + taste_description = "blue" + ph = 11 + burning_temperature = 20 //cold burning + burning_volume = 0.1 + var/organ + +/datum/reagent/medicine/cryodrit_base/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (affected_mob.bodytemperature ** 2) + 0.1) + if(affected_mob.bodytemperature >= 173.15 || affected_mob.bodytemperature <= 33 || !HAS_TRAIT(affected_mob, TRAIT_KNOCKEDOUT)) + return + var/power = -0.25 * ((-(affected_mob.bodytemperature - 103)^2 / 2450) + 2) + var/need_mob_update + var/obj/item/organ/internal/precode_organ = affected_mob.get_organ_slot(organ) + if (precode_organ && IS_ORGANIC_ORGAN(precode_organ) && !(precode_organ.organ_flags & ORGAN_FAILING)) + need_mob_update = affected_mob.adjustOrganLoss(organ, power * REM * seconds_per_tick, ) + if(need_mob_update) + return UPDATE_MOB_HEALTH + +//Кардиокcадонорит - сердце + +/datum/reagent/medicine/cryodrit_base/cardioxadonorit + name = "Cardioxadonorit" + description = "Used to heal heart in cold enviroment. Optimal temperature for best result is 103K" + organ = ORGAN_SLOT_HEART + +/datum/chemical_reaction/medicine/cardioxadonorit + results = list(/datum/reagent/medicine/cryodrit_base/ = 5) + required_reagents = list(/datum/reagent/medicine/omnidrite = 3, /datum/reagent/medicine/cryoxadone = 2, /datum/reagent/acetone = 1, /datum/reagent/water = 1, /datum/reagent/medicine/ephedrine = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY + +//Пульмеокcадонорит - легкие + +/datum/reagent/medicine/cryodrit_base/pulmeoxadonorit + name = "Pulmeoxadonorit" + description = "Used to heal lungs in cold enviroment. Optimal temperature for best result is 103K" + organ = ORGAN_SLOT_LUNGS + +/datum/chemical_reaction/medicine/pulmeoxadonorit + results = list(/datum/reagent/medicine/cryodrit_base/ = 5) + required_reagents = list(/datum/reagent/medicine/omnidrite = 3, /datum/reagent/medicine/cryoxadone = 2, /datum/reagent/acetone = 1, /datum/reagent/water = 1, /datum/reagent/medicine/salbutamol = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY + +//Гигакcадонорит - печень + +/datum/reagent/medicine/cryodrit_base/higaxadonorit + name = "Higaxadonorit" + description = "Used to heal liver in cold enviroment. Optimal temperature for best result is 103K" + organ = ORGAN_SLOT_LIVER + +/datum/chemical_reaction/medicine/higaxadonorit + results = list(/datum/reagent/medicine/cryodrit_base/ = 5) + required_reagents = list(/datum/reagent/medicine/omnidrite = 3, /datum/reagent/medicine/cryoxadone = 2, /datum/reagent/acetone = 1, /datum/reagent/water = 1, /datum/reagent/medicine/higadrite = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY + +//Гастрокcадонорит - желудок + +/datum/reagent/medicine/cryodrit_base/gastroxadonorit + name = "Gastroxadonorit" + description = "Used to heal stomach in cold enviroment. Optimal temperature for best result is 103K" + organ = ORGAN_SLOT_STOMACH + +/datum/chemical_reaction/medicine/gastroxadonorit + results = list(/datum/reagent/medicine/cryodrit_base/ = 5) + required_reagents = list(/datum/reagent/medicine/omnidrite = 3, /datum/reagent/medicine/cryoxadone = 2, /datum/reagent/acetone = 1, /datum/reagent/water = 1, /datum/reagent/medicine/calomel = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_BRUTE |REACTION_TAG_BURN | REACTION_TAG_TOXIN | REACTION_TAG_OXY diff --git a/modular_bandastation/medical/code/new_chemicals/painkillers.dm b/modular_bandastation/medical/code/new_chemicals/painkillers.dm new file mode 100644 index 0000000000000..c7f372a12ab29 --- /dev/null +++ b/modular_bandastation/medical/code/new_chemicals/painkillers.dm @@ -0,0 +1,123 @@ +/datum/reagent/medicine/mine_salve + name = "Miner's Salve" + description = "A powerful painkiller. Restores bruising and burns in addition to making the patient believe they are fully healed. Also great for treating severe burn wounds in a pinch." + reagent_state = LIQUID + color = "#6D6374" + metabolization_rate = 0.4 * REAGENTS_METABOLISM + ph = 2.6 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS + metabolized_traits = list() + +/datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update + affected_mob.apply_status_effect(/datum/status_effect/painkiller/low) + need_mob_update = affected_mob.adjustBruteLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + need_mob_update += affected_mob.adjustFireLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/medicine/morphine + name = "Morphine" + description = "A painkiller that allows the patient to move at full speed even when injured. Causes drowsiness and eventually unconsciousness in high doses. Overdose will cause a variety of effects, ranging from minor to lethal." + reagent_state = LIQUID + color = "#A9FBFB" + metabolization_rate = 0.5 * REAGENTS_METABOLISM + overdose_threshold = 30 + ph = 8.96 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + addiction_types = list(/datum/addiction/opioids = 10) + metabolized_traits = list() + +/datum/reagent/medicine/morphine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.apply_status_effect(/datum/status_effect/painkiller/medium) + if(current_cycle > 5) + affected_mob.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) + switch(current_cycle) + if(12) + to_chat(affected_mob, span_warning("You start to feel tired...") ) + if(13 to 25) + affected_mob.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick) + if(25 to INFINITY) + affected_mob.Sleeping(40 * REM * seconds_per_tick) + +/datum/reagent/medicine/muscle_stimulant + name = "Muscle Stimulant" + description = "A potent chemical that allows someone under its influence to be at full physical ability even when under massive amounts of pain." + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE + addiction_types = list(/datum/addiction/opioids = 5) + metabolized_traits = list() + +/datum/reagent/medicine/muscle_stimulant/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.apply_status_effect(/datum/status_effect/painkiller/medium) + +/datum/reagent/drug/bath_salts + name = "Bath Salts" + description = "Makes you impervious to stuns and grants a stamina regeneration buff, but you will be a nearly uncontrollable tramp-bearded raving lunatic." + reagent_state = LIQUID + color = "#FAFAFA" + overdose_threshold = 20 + taste_description = "salt" // because they're bathsalts? + addiction_types = list(/datum/addiction/stimulants = 25) //8 per 2 seconds + ph = 8.2 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + metabolized_traits = list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE, TRAIT_STIMULATED) + +/datum/reagent/drug/bath_salts/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.apply_status_effect(/datum/status_effect/painkiller/high) + var/high_message = pick("You feel amped up.", "You feel ready.", "You feel like you can push it to the limit.") + if(SPT_PROB(2.5, seconds_per_tick)) + to_chat(affected_mob, span_notice("[high_message]")) + affected_mob.add_mood_event("salted", /datum/mood_event/stimulant_heavy) + var/need_mob_update + need_mob_update = affected_mob.adjustStaminaLoss(-6 * REM * seconds_per_tick, updating_stamina = FALSE, required_biotype = affected_biotype) + need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags) + affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) + if(need_mob_update) + . = UPDATE_MOB_HEALTH + if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !ismovable(affected_mob.loc)) + step(affected_mob, pick(GLOB.cardinals)) + step(affected_mob, pick(GLOB.cardinals)) + + +/datum/reagent/drug/pumpup + name = "Pump-Up" + description = "Take on the world! A fast acting, hard hitting drug that pushes the limit on what you can handle." + reagent_state = LIQUID + color = "#e38e44" + metabolization_rate = 2 * REAGENTS_METABOLISM + overdose_threshold = 30 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + addiction_types = list(/datum/addiction/stimulants = 6) //2.6 per 2 seconds + metabolized_traits = list(TRAIT_BATON_RESISTANCE, TRAIT_STIMULATED) + +/datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick) + affected_mob.apply_status_effect(/datum/status_effect/painkiller/high) + if(SPT_PROB(2.5, seconds_per_tick)) + to_chat(affected_mob, span_notice("[pick("Go! Go! GO!", "You feel ready...", "You feel invincible...")]")) + if(SPT_PROB(7.5, seconds_per_tick)) + affected_mob.losebreath++ + affected_mob.adjustToxLoss(2, updating_health = FALSE, required_biotype = affected_biotype) + return UPDATE_MOB_HEALTH + +/datum/reagent/drug/maint/sludge + name = "Maintenance Sludge" + description = "An unknown sludge that you most likely gotten from an assistant, a bored chemist... or cooked yourself. Half refined, it fills your body with itself, making it more resistant to wounds, but causes toxins to accumulate." + reagent_state = LIQUID + color = "#203d2c" + metabolization_rate = 2 * REAGENTS_METABOLISM + overdose_threshold = 25 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + addiction_types = list(/datum/addiction/maintenance_drugs = 8) + metabolized_traits = list(TRAIT_HARDLY_WOUNDED) + +/datum/reagent/drug/maint/sludge/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.apply_status_effect(/datum/status_effect/painkiller/high) + if(affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, required_biotype = affected_biotype)) + return UPDATE_MOB_HEALTH diff --git a/modular_bandastation/medical/code/new_chemicals/unique_chemicals.dm b/modular_bandastation/medical/code/new_chemicals/unique_chemicals.dm new file mode 100644 index 0000000000000..a4475bee364ef --- /dev/null +++ b/modular_bandastation/medical/code/new_chemicals/unique_chemicals.dm @@ -0,0 +1,83 @@ +//Омнидрит - немного восстанавливает органы пока в организме, является основной для лекарств органов +/datum/reagent/medicine/omnidrite + name = "Omnidrite" + description = "Stimulates the slow healing of organs. Have extremly low threshold of overdose." + reagent_state = LIQUID + color = "#05af85" + metabolization_rate = 0.25 * REAGENTS_METABOLISM + overdose_threshold = 10 + ph = 10.7 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + inverse_chem_val = 0.3 + inverse_chem = /datum/reagent/medicine/higadrite + var/static/list/possible_organs = list( + ORGAN_SLOT_HEART, + ORGAN_SLOT_LIVER, + ORGAN_SLOT_LUNGS, + ORGAN_SLOT_STOMACH, + ORGAN_SLOT_EYES, + ORGAN_SLOT_EARS, + ORGAN_SLOT_BRAIN, + ORGAN_SLOT_APPENDIX, + ORGAN_SLOT_TONGUE, + ) + +/datum/reagent/medicine/omnidrite/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/selected = pick(possible_organs) + var/obj/item/organ/internal/organ = affected_mob.get_organ_slot(selected) + if(organ && IS_ORGANIC_ORGAN(organ) && !(organ.organ_flags & ORGAN_FAILING)) + affected_mob.adjustOrganLoss(selected, -0.5 * REM * seconds_per_tick * normalise_creation_purity()) + +/datum/reagent/medicine/omnidrite/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/selected = pick(possible_organs) + var/obj/item/organ/internal/organ = affected_mob.get_organ_slot(selected) + if(organ && IS_ORGANIC_ORGAN(organ)) + affected_mob.adjustOrganLoss(selected, 3 * REM * seconds_per_tick * normalise_creation_purity()) + affected_mob.reagents.remove_reagent(type, 1 * REM * seconds_per_tick) + +/datum/chemical_reaction/medicine/omnidrite + results = list(/datum/reagent/medicine/omnidrite = 3) + required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/phenol = 1, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_BURN + + +//Некроинверсит - позволяет начать реверс некрозиса на 1 и 2 стадиях +/datum/reagent/medicine/spaceacillin + name = "Spaceacillin" + description = "Spaceacillin will provide limited resistance against disease and parasites. Also reduces infection in serious burns." + color = "#E1F2E6" + metabolization_rate = 0.1 * REAGENTS_METABOLISM + ph = 8.1 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + added_traits = list(TRAIT_VIRUS_RESISTANCE) + inverse_chem_val = 0.5 + inverse_chem = /datum/reagent/medicine/spaceacillin + +/datum/reagent/medicine/necroinversite + name = "Necroinversite" + description = "Stimulates the cells for reverse necrosis process. Absolutely useless on last stage of necrosis. Can help with some toxin damage" + reagent_state = LIQUID + color = "#813d8f" + metabolization_rate = 0.5 * REAGENTS_METABOLISM + overdose_threshold = 30 + ph = 4.1 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + + +/datum/reagent/medicine/necroinversite/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.apply_status_effect(/datum/status_effect/necroinversite) + var/need_mob_update + if(affected_mob.getToxLoss() <= 15) + need_mob_update = affected_mob.adjustToxLoss(-0.3, updating_health = FALSE, required_biotype = affected_biotype) + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/medicine/necroinversite/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/need_mob_update + need_mob_update = affected_mob.adjustBruteLoss(1.5, updating_health = FALSE, required_bodytype = affected_bodytype) + if(need_mob_update) + return UPDATE_MOB_HEALTH diff --git a/modular_bandastation/medical/code/new_quirks/professional_doctor.dm b/modular_bandastation/medical/code/new_quirks/professional_doctor.dm new file mode 100644 index 0000000000000..fab34a72d127c --- /dev/null +++ b/modular_bandastation/medical/code/new_quirks/professional_doctor.dm @@ -0,0 +1,9 @@ +/datum/quirk/item_quirk/professional_doctor + name = "Professional doctor" + desc = "After years of practice you got a masterful skill of eximines limb of your pateins. Just grab him and carefully examine." + icon = FA_ICON_MEDKIT + value = 2 + mob_trait = TRAIT_PROFESSIONAL_DOCTOR + gain_text = span_notice("You perfectly know, how to diagnose limbs!") + lose_text = span_danger("Suddenly, you feel like you lose your knowledge of limbs examinations!") + medical_record_text = "Patient have a masterfull skills to check conditions of limbs." diff --git a/modular_bandastation/medical/code/new_quirks/professional_surgeon.dm b/modular_bandastation/medical/code/new_quirks/professional_surgeon.dm new file mode 100644 index 0000000000000..0547fab2002ac --- /dev/null +++ b/modular_bandastation/medical/code/new_quirks/professional_surgeon.dm @@ -0,0 +1,9 @@ +/datum/quirk/item_quirk/professional_surgeon + name = "Professional surgeon" + desc = "After years of practice you got a masterful skill in surgery. Reduces chances of fail surgery step and infections." + icon = FA_ICON_PRAYING_HANDS + value = 4 + mob_trait = TRAIT_PROFESSIONAL_SURGEON + gain_text = span_notice("You are masterfull in operations in any condition!") + lose_text = span_danger("Do you ever was a master in operations?") + medical_record_text = "Patient have a license of professional surgeon." diff --git a/modular_bandastation/medical/code/new_skillchips/surgery_chip_chance.dm b/modular_bandastation/medical/code/new_skillchips/surgery_chip_chance.dm new file mode 100644 index 0000000000000..272df607e8eb4 --- /dev/null +++ b/modular_bandastation/medical/code/new_skillchips/surgery_chip_chance.dm @@ -0,0 +1,9 @@ +/obj/item/skillchip/job/medic + name = "Medical R3-SC-U3 Circuitry" + desc = "Aproved by Runtime." + auto_traits = list(TRAIT_SURGEON_SKILL) + skill_name = "Medical Circuitry" + skill_description = "Help with speed and protection from failing surgery steps." + skill_icon = "sitemap" + activate_message = "You suddenly understand how to be better surgeon." + deactivate_message = "It's time to get back to school for learning surgery." diff --git a/modular_bandastation/medical/code/new_surgery/organ_surgery_fails.dm b/modular_bandastation/medical/code/new_surgery/organ_surgery_fails.dm new file mode 100644 index 0000000000000..08c479afea094 --- /dev/null +++ b/modular_bandastation/medical/code/new_surgery/organ_surgery_fails.dm @@ -0,0 +1,106 @@ +/datum/surgery_step/lobectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + display_results( + user, + target, + span_warning("You screw up, failing to excise [human_target]'s damaged lobe!"), + span_warning("[user] screws up!"), + span_warning("[user] screws up!"), + ) + display_pain(target, "You feel a sharp stab in your chest; the wind is knocked out of you and it hurts to catch your breath!") + human_target.losebreath += 4 + human_target.adjustOrganLoss(ORGAN_SLOT_LUNGS, 10) + human_target.adjustOrganScarring(ORGAN_SLOT_LUNGS) + return FALSE + +/datum/surgery_step/coronary_bypass/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(ishuman(target)) + var/mob/living/carbon/human/target_human = target + display_results( + user, + target, + span_warning("You screw up in attaching the graft, and it tears off, tearing part of the heart!"), + span_warning("[user] screws up, causing blood to spurt out of [target_human]'s chest profusely!"), + span_warning("[user] screws up, causing blood to spurt out of [target_human]'s chest profusely!"), + ) + display_pain(target, "Your chest burns; you feel like you're going insane!") + target_human.adjustOrganLoss(ORGAN_SLOT_HEART, 20) + target_human.adjustOrganScarring(ORGAN_SLOT_HEART) + var/obj/item/bodypart/target_bodypart = target_human.get_bodypart(target_zone) + target_bodypart.adjustBleedStacks(30) + return FALSE + +/datum/surgery_step/fix_ears/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(target.get_organ_by_type(/obj/item/organ/internal/brain)) + display_results( + user, + target, + span_warning("You accidentally stab [target] right in the brain!"), + span_warning("[user] accidentally stabs [target] right in the brain!"), + span_warning("[user] accidentally stabs [target] right in the brain!"), + ) + display_pain(target, "You feel a visceral stabbing pain right through your head, into your brain!") + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) + target.adjustOrganScarring(ORGAN_SLOT_EARS) + else + display_results( + user, + target, + span_warning("You accidentally stab [target] right in the brain! Or would have, if [target] had a brain."), + span_warning("[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain."), + span_warning("[user] accidentally stabs [target] right in the brain!"), + ) + display_pain(target, "You feel a visceral stabbing pain right through your head!") // dunno who can feel pain w/o a brain but may as well be consistent. + target.adjustOrganScarring(ORGAN_SLOT_EARS) + return FALSE + +/datum/surgery_step/fix_eyes/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(target.get_organ_by_type(/obj/item/organ/internal/brain)) + display_results( + user, + target, + span_warning("You accidentally stab [target] right in the brain!"), + span_warning("[user] accidentally stabs [target] right in the brain!"), + span_warning("[user] accidentally stabs [target] right in the brain!"), + ) + display_pain(target, "You feel a visceral stabbing pain right through your head, into your brain!") + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) + target.adjustOrganScarring(ORGAN_SLOT_EYES) + else + display_results( + user, + target, + span_warning("You accidentally stab [target] right in the brain! Or would have, if [target] had a brain."), + span_warning("[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain."), + span_warning("[user] accidentally stabs [target] right in the brain!"), + ) + display_pain(target, "You feel a visceral stabbing pain right through your head!") // dunno who can feel pain w/o a brain but may as well be consistent. + target.adjustOrganScarring(ORGAN_SLOT_EYES) + return FALSE + +/datum/surgery_step/gastrectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery) + var/mob/living/carbon/human/target_human = target + target_human.adjustOrganLoss(ORGAN_SLOT_STOMACH, 15) + target_human.adjustOrganScarring(ORGAN_SLOT_STOMACH) + display_results( + user, + target, + span_warning("You cut the wrong part of [target]'s stomach!"), + span_warning("[user] cuts the wrong part of [target]'s stomach!"), + span_warning("[user] cuts the wrong part of [target]'s stomach!"), + ) + display_pain(target, "Your stomach throbs with pain; it's not getting any better!") + +/datum/surgery_step/hepatectomy/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery) + var/mob/living/carbon/human/human_target = target + human_target.adjustOrganLoss(ORGAN_SLOT_LIVER, 15) + human_target.adjustOrganScarring(ORGAN_SLOT_LIVER) + display_results( + user, + target, + span_warning("You cut the wrong part of [target]'s liver!"), + span_warning("[user] cuts the wrong part of [target]'s liver!"), + span_warning("[user] cuts the wrong part of [target]'s liver!"), + ) + display_pain(target, "You feel a sharp stab inside your abdomen!") diff --git a/modular_bandastation/medical/code/new_surgery/steps_remake.dm b/modular_bandastation/medical/code/new_surgery/steps_remake.dm new file mode 100644 index 0000000000000..484f3a7e54064 --- /dev/null +++ b/modular_bandastation/medical/code/new_surgery/steps_remake.dm @@ -0,0 +1,99 @@ +/datum/surgery/debride + name = "Debride burnt flesh" + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB + targetable_wound = list(/datum/wound/burn/flesh,/datum/wound/necrosis/basic_necro) + possible_locs = list( + BODY_ZONE_R_ARM, + BODY_ZONE_L_ARM, + BODY_ZONE_R_LEG, + BODY_ZONE_L_LEG, + BODY_ZONE_CHEST, + BODY_ZONE_HEAD, + ) + steps = list( + /datum/surgery_step/debride, + /datum/surgery_step/dress, + ) + +/datum/surgery/debride/can_start(mob/living/user, mob/living/carbon/target) + . = ..() + if(!.) + return . + + var/datum/wound/burn/flesh/burn_wound = target.get_bodypart(user.zone_selected).get_wound_type(/datum/wound/burn/flesh) + var/datum/wound/necrosis/basic_necro/necro_wound = target.get_bodypart(user.zone_selected).get_wound_type(/datum/wound/necrosis/basic_necro) + // Should be guaranteed to have the wound by this point + ASSERT(burn_wound, "[type] on [target] has no burn or infected wound when it should have been guaranteed to have one by can_start") + if (!isnull(burn_wound)) + return burn_wound.infestation > 0 + if (!isnull(necro_wound)) + return necro_wound.necrosing_progress > 0 + +/datum/surgery_step/debride + name = "excise infection (hemostat)" + implements = list( + TOOL_HEMOSTAT = 100, + TOOL_SCALPEL = 85, + TOOL_SAW = 60, + TOOL_WIRECUTTER = 40) + time = 30 + repeatable = TRUE + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' + failure_sound = 'sound/surgery/organ1.ogg' + surgery_effects_mood = TRUE + + var/necrosis_removed = 4 + +/datum/surgery_step/debride/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) + var/datum/wound/necrosis/basic_necro/necro_wound + var/list_data = target.get_wounded_bodyparts() + for(var/obj/item/bodypart/limb in (list_data)) + for(var/limb_wound in limb.wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/necrosis/basic_necro/)) + necro_wound = current_wound + var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound + if(burn_wound) + var/progress_text = get_progress(user, target, burn_wound) + display_results( + user, + target, + span_notice("You successfully excise some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)][progress_text]."), + span_notice("[user] successfully excises some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), + span_notice("[user] successfully excises some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), + ) + log_combat(user, target, "excised infected flesh in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") + surgery.operated_bodypart.receive_damage(brute=3, wound_bonus=CANT_WOUND) + burn_wound.infestation -= infestation_removed + burn_wound.sanitization += sanitization_added + necro_wound.necrosing_progress -= necrosis_removed + if(burn_wound.infestation <= 0) + repeatable = FALSE + else + to_chat(user, span_warning("[target] has no infected flesh there!")) + return ..() + +/datum/surgery_step/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) + var/screwedmessage = "" + switch(fail_prob) + if(0 to 24) + screwedmessage = " You almost had it, though." + if(50 to 74)//25 to 49 = no extra text + screwedmessage = " This is hard to get right in these conditions..." + if(75 to 99) + screwedmessage = " This is practically impossible in these conditions..." + + display_results( + user, + target, + span_warning("You screw up![screwedmessage]"), + span_warning("[user] screws up!"), + span_notice("[user] finishes."), TRUE) //By default the patient will notice if the wrong thing has been cut + + if (rand(1,100) > 20) + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(surgery.operated_bodypart,silent = TRUE) + + return FALSE diff --git a/modular_bandastation/medical/code/new_surgery/surgery_fail_chance.dm b/modular_bandastation/medical/code/new_surgery/surgery_fail_chance.dm new file mode 100644 index 0000000000000..7726373f30dc4 --- /dev/null +++ b/modular_bandastation/medical/code/new_surgery/surgery_fail_chance.dm @@ -0,0 +1,149 @@ +#define SURGERY_SLOWDOWN_CAP_MULTIPLIER 2.5 +#define SURGERY_SPEED_MORBID_CURIOSITY 0.7 +#define SURGERY_STATE_STARTED "surgery_started" +#define SURGERY_STATE_FAILURE "surgery_failed" +#define SURGERY_STATE_SUCCESS "surgery_success" +#define SURGERY_SPEED_DISSECTION_MODIFIER 0.8 +#define SURGERY_SPEED_TRAIT_ANALGESIA 0.8 + +/datum/surgery_step/initiate(mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + // Only followers of Asclepius have the ability to use Healing Touch and perform miracle feats of surgery. + // Prevents people from performing multiple simultaneous surgeries unless they're holding a Rod of Asclepius. + + surgery.step_in_progress = TRUE + var/speed_mod = 1 + var/fail_prob = 0//100 - fail_prob = success_prob + var/advance = FALSE + + if(preop(user, target, target_zone, tool, surgery) == SURGERY_STEP_FAIL) + update_surgery_mood(target, SURGERY_STATE_FAILURE) + surgery.step_in_progress = FALSE + return FALSE + + update_surgery_mood(target, SURGERY_STATE_STARTED) + play_preop_sound(user, target, target_zone, tool, surgery) // Here because most steps overwrite preop + + if(tool) + speed_mod = tool.toolspeed + + if(HAS_TRAIT(target, TRAIT_SURGICALLY_ANALYZED)) + speed_mod *= SURGERY_SPEED_DISSECTION_MODIFIER + + if(check_morbid_curiosity(user, tool, surgery)) + speed_mod *= SURGERY_SPEED_MORBID_CURIOSITY + + if(HAS_TRAIT(target, TRAIT_ANALGESIA)) + speed_mod *= SURGERY_SPEED_TRAIT_ANALGESIA + + var/implement_speed_mod = 1 + if(implement_type) //this means it isn't a require hand or any item step. + implement_speed_mod = implements[implement_type] / 100.0 + + speed_mod /= (get_location_modifier(target) * (1 + surgery.speed_modifier) * implement_speed_mod) * target.mob_surgery_speed_mod + var/modded_time = time * speed_mod + + var/turf/user_turf = user.loc + fail_prob = 70 + if(tool) + fail_prob -= (1/tool.toolspeed) * 10 + else + fail_prob += 10 + fail_prob = fail_prob - 3 * user_turf.get_lumcount() + if (HAS_TRAIT(target, TRAIT_ANALGESIA)) + fail_prob -= 30 + else if (target.has_status_effect(/datum/status_effect/painkiller/high)) + fail_prob -= 20 + else if (target.has_status_effect(/datum/status_effect/painkiller/medium)) + fail_prob -= 15 + else if (target.has_status_effect(/datum/status_effect/painkiller/low)) + fail_prob -= 10 + else if (target.get_drunk_amount() > 0) + fail_prob -= target.get_drunk_amount()/4 + else + fail_prob += 15 + if (HAS_TRAIT(user, TRAIT_SURGEON_SKILL)) + fail_prob -= 30 + if (HAS_TRAIT(user, TRAIT_PROFESSIONAL_SURGEON)) + fail_prob -= 15 + + var/necro_prob = 0 + var/obj/item/clothing/gloves/gloves = user.get_item_by_slot(ITEM_SLOT_HANDS) + var/obj/item/clothing/mask/mask = user.get_item_by_slot(ITEM_SLOT_MASK) + if(GET_ATOM_BLOOD_DNA_LENGTH(target.loc)) + necro_prob += 10 + var/mob/living/carbon/surgeon = user + if(isnull(gloves)) + necro_prob += 20 + else + necro_prob -= surgeon.gloves.get_armor_rating(BIO) / 5 + if(GET_ATOM_BLOOD_DNA_LENGTH(gloves)) + necro_prob += 30 + if(isnull(mask)) + necro_prob += 5 + else + necro_prob -= surgeon.head.get_armor_rating(BIO) / 5 + if(GET_ATOM_BLOOD_DNA_LENGTH(mask)) + necro_prob += 10 + if(GET_ATOM_BLOOD_DNA_LENGTH(tool)) + necro_prob += 30 + if(target.reagents.get_reagent_amount(/datum/reagent/space_cleaner/sterilizine) ) + necro_prob -= 10 + if(target.get_drunk_amount() > 0) + necro_prob -= 5 + if(HAS_TRAIT(target, TRAIT_ANALGESIA)) + necro_prob -= 10 + + fail_prob = fail_prob + min(max(0, modded_time - (time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)),99)//if modded_time > time * modifier, then fail_prob = modded_time - time*modifier. starts at 0, caps at 99 + modded_time = min(modded_time, time * SURGERY_SLOWDOWN_CAP_MULTIPLIER)//also if that, then cap modded_time at time*modifier + + if(iscyborg(user))//any immunities to surgery slowdown should go in this check. + modded_time = time * tool.toolspeed + + var/was_sleeping = (target.stat != DEAD && target.IsSleeping()) + + if (prob(necro_prob)) + var/founded_necrosis = FALSE + var/datum/wound/necrosis/basic_necro/trauma + var/obj/item/bodypart/limb = target_zone + var/wounds = limb.wounds + for(var/limb_wound in wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/necrosis/basic_necro)) + founded_necrosis = TRUE + trauma = current_wound + if (founded_necrosis) + trauma.necrosing_progress += necro_prob + else + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(target_zone,silent = TRUE,wound_source = "during surgery") + + if(do_after(user, modded_time, target = target, interaction_key = user.has_status_effect(/datum/status_effect/hippocratic_oath) ? target : DOAFTER_SOURCE_SURGERY)) //If we have the hippocratic oath, we can perform one surgery on each target, otherwise we can only do one surgery in total. + + var/chem_check_result = chem_check(target) + if((prob(100-fail_prob) || (iscyborg(user) && !silicons_obey_prob)) && chem_check_result && !try_to_fail) + + if(success(user, target, target_zone, tool, surgery)) + update_surgery_mood(target, SURGERY_STATE_SUCCESS) + play_success_sound(user, target, target_zone, tool, surgery) + advance = TRUE + else + if(failure(user, target, target_zone, tool, surgery, fail_prob)) + play_failure_sound(user, target, target_zone, tool, surgery) + update_surgery_mood(target, SURGERY_STATE_FAILURE) + advance = TRUE + if(chem_check_result) + return .(user, target, target_zone, tool, surgery, try_to_fail) //automatically re-attempt if failed for reason other than lack of required chemical + if(advance && !repeatable) + surgery.status++ + if(surgery.status > surgery.steps.len) + surgery.complete(user) + + else if(!QDELETED(target)) + update_surgery_mood(target, SURGERY_STATE_FAILURE) + + if(target.stat == DEAD && was_sleeping && user.client) + user.client.give_award(/datum/award/achievement/jobs/sandman, user) + + surgery.step_in_progress = FALSE + return advance diff --git a/modular_bandastation/medical/code/new_surgery/surgery_iternalbleed.dm b/modular_bandastation/medical/code/new_surgery/surgery_iternalbleed.dm new file mode 100644 index 0000000000000..6c6cdddf0e812 --- /dev/null +++ b/modular_bandastation/medical/code/new_surgery/surgery_iternalbleed.dm @@ -0,0 +1,127 @@ +/datum/surgery/iternal_bleed + name = "Internal bleeding treatment" + surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_REQUIRES_REAL_LIMB + targetable_wound = /datum/wound/internal_bleed/bleed + possible_locs = list( + BODY_ZONE_R_ARM, + BODY_ZONE_L_ARM, + BODY_ZONE_R_LEG, + BODY_ZONE_L_LEG, + BODY_ZONE_CHEST, + BODY_ZONE_HEAD, + ) + steps = list( + /datum/surgery_step/incise, + /datum/surgery_step/retract_skin, + /datum/surgery_step/clamp_bleeders, + /datum/surgery_step/fix_ib, + /datum/surgery_step/close + ) + +/datum/surgery/fix_ib/can_start(mob/living/user, mob/living/carbon/target) + . = ..() + if(!.) + return . + + var/datum/wound/internal_bleed/bleed/ib_wound = target.get_bodypart(user.zone_selected).get_wound_type(targetable_wound) + // Should be guaranteed to have the wound by this point + ASSERT(ib_wound, "[type] on [target] has no internal bleeding and operation can not be started") + return ib_wound.blood_flow > 0 + +//SURGERY STEPS + +///// Debride +/datum/surgery_step/fix_ib + name = "fix internal bleeding (hemostat)" + implements = list( + TOOL_HEMOSTAT = 100, + TOOL_SCALPEL = 85, + TOOL_WIRECUTTER = 60, + TOOL_SAW = 40) + time = 30 + repeatable = TRUE + preop_sound = 'sound/surgery/scalpel1.ogg' + success_sound = 'sound/surgery/retractor2.ogg' + failure_sound = 'sound/surgery/organ1.ogg' + surgery_effects_mood = TRUE + /// How much infestation is removed per step (positive number) + var/blood_flow_removed = 4 + +/// To give the surgeon a heads up how much work they have ahead of them +/datum/surgery_step/fix_ib/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/internal_bleed/bleed/ib_wound) + if(!ib_wound?.blood_flow || !blood_flow_removed) + return + var/estimated_remaining_steps = ib_wound.blood_flow / blood_flow_removed + var/progress_text + + switch(estimated_remaining_steps) + if(-INFINITY to 1) + return + if(1 to 2) + progress_text = ", preparing to remove the last remaining bits of infection" + if(2 to 4) + progress_text = ", steadily narrowing the remaining bits of infection" + if(5 to INFINITY) + progress_text = ", though there's still quite a lot to excise" + + return progress_text + +/datum/surgery_step/fix_ib/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(surgery.operated_wound) + var/datum/wound/internal_bleed/bleed/IB_wound = surgery.operated_wound + if(IB_wound.blood_flow <= 0) + to_chat(user, span_notice("[target]'s [target.parse_zone_with_bodypart(user.zone_selected)] has no internal traumas to remove!")) + surgery.status++ + repeatable = FALSE + return + display_results( + user, + target, + span_notice("You begin to fix internal traumas from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]..."), + span_notice("[user] begins to fix internal traumas from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)] with [tool]."), + span_notice("[user] begins to fix internal traumas from [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), + ) + display_pain(target, "The internal bleeding in your [target.parse_zone_with_bodypart(user.zone_selected)] stings like hell! It feels like you're being teared apart!") + else + user.visible_message(span_notice("[user] looks for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]."), span_notice("You look for [target]'s [target.parse_zone_with_bodypart(user.zone_selected)]...")) + +/datum/surgery_step/fix_ib/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE) + var/datum/wound/internal_bleed/bleed/IB_wound = surgery.operated_wound + if(IB_wound) + var/progress_text = get_progress(user, target, IB_wound) + display_results( + user, + target, + span_notice("You successfully fix internal traumasfrom [target]'s [target.parse_zone_with_bodypart(target_zone)][progress_text]."), + span_notice("[user] successfully fix internal traumas from [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), + span_notice("[user] successfully fix internal traumas from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), + ) + log_combat(user, target, "fixed internal traumas in", addition="COMBAT MODE: [uppertext(user.combat_mode)]") + surgery.operated_bodypart.receive_damage(brute=2, wound_bonus=CANT_WOUND) + IB_wound.blood_flow -= blood_flow_removed + if(IB_wound.blood_flow <= 0) + repeatable = FALSE + else + to_chat(user, span_warning("[target] has no internal traumas there!")) + return ..() + +/datum/surgery_step/fix_ib/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob = 0) + ..() + var/datum/wound/internal_bleed/bleed/IB_wound = surgery.operated_wound + display_results( + user, + target, + span_notice("You fingers slip and you accidently damaged some insides in [target]'s [target.parse_zone_with_bodypart(target_zone)]."), + span_notice("[user] fingers slip and you accidently damaged some insides in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"), + span_notice("[user] fingers slip and you accidently damaged some insides in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"), + ) + surgery.operated_bodypart.receive_damage(brute=rand(3,5), sharpness=TRUE) + IB_wound.blood_flow += blood_flow_removed + +/datum/surgery_step/fix_ib/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + if(!..()) + return + var/datum/wound/internal_bleed/bleed/IB_wound = surgery.operated_wound + while(IB_wound && IB_wound.blood_flow > 0.25) + if(!..()) + break diff --git a/modular_bandastation/medical/code/new_technologies/medical_designs.dm b/modular_bandastation/medical/code/new_technologies/medical_designs.dm new file mode 100644 index 0000000000000..ec270230f26c4 --- /dev/null +++ b/modular_bandastation/medical/code/new_technologies/medical_designs.dm @@ -0,0 +1,170 @@ +/datum/techweb_node/surgery_tools + id = "surgery_tools" + display_name = "Advanced Surgery Tools" + description = "Surgical instruments of dual purpose for quick operations." + prereq_ids = list("surgery_exp") + design_ids = list( + "laserscalpel", + "searingtool", + "mechanicalpinches", + "autocompressor" + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS) + discount_experiments = list(/datum/experiment/autopsy/xenomorph = TECHWEB_TIER_4_POINTS) + +/datum/design/autocompressor + name = "Autocompressor" + desc = "A chest mounted device for automatic CPR in oxygen enviroment." + id = "autocompressor" + build_path = /obj/item/clothing/suit/autocompressor + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT * 1, /datum/material/silver =SHEET_MATERIAL_AMOUNT, /datum/material/gold =HALF_SHEET_MATERIAL_AMOUNT * 0.5, /datum/material/diamond =SMALL_MATERIAL_AMOUNT * 1, /datum/material/titanium = SHEET_MATERIAL_AMOUNT*1) + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_MEDICAL_ADVANCED + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + + +/datum/techweb_node/medbay_equip_adv + id = "medbay_equip_adv" + display_name = "Advanced Medbay Equipment" + description = "State-of-the-art medical gear for keeping the crew in one piece — mostly." + prereq_ids = list("cryostasis") + design_ids = list( + "chem_mass_spec", + "healthanalyzer_advanced", + "mod_health_analyzer", + "crewpinpointer", + "defibrillator_compact", + "defibmount", + "medicalbed_emergency", + "fieldkit", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS) + +/datum/design/field_kit + name = "Field Kit" + id = "fieldkit" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*0.33) + build_path = /obj/item/storage/field_kit + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_CHEMISTRY, + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/techweb_node/chem_synthesis + id = "chem_synthesis" + display_name = "Chemical Synthesis" + description = "Synthesizing complex chemicals from electricity and thin air... Don't ask how..." + prereq_ids = list("medbay_equip") + design_ids = list( + "xlarge_beaker", + "blood_pack", + "chem_pack", + "med_spray_bottle", + "medigel", + "medipen_refiller", + "soda_dispenser", + "beer_dispenser", + "chem_dispenser", + "portable_chem_mixer", + "chem_heater", + "w-recycler", + "chembag", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) + +/datum/design/chem_bag + name = "Chemistry holding bag" + id = "chembag" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/plastic = SMALL_MATERIAL_AMOUNT*0.5) + build_path = /obj/item/storage/chem_bag + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_CHEMISTRY, + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/techweb_node/surgery_adv + id = "surgery_adv" + display_name = "Advanced Surgery" + description = "When simple medicine doesn't cut it." + prereq_ids = list("surgery") + design_ids = list( + "harvester", + "surgery_heal_brute_upgrade_femto", + "surgery_heal_burn_upgrade_femto", + "surgery_heal_combo", + "surgery_lobotomy", + "surgery_wing_reconstruction", + "surgerytablev2", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS) + required_experiments = list(/datum/experiment/autopsy/human) + +/datum/design/surgerytablev2 + name = "Advanced surgery table" + id = "surgerytablev2" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*0.4, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.1) + build_path = /obj/item/circuitboard/machine/surgerytablev2 + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_CHEMISTRY, + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL + +/datum/techweb_node/alien_surgery + id = "alien_surgery" + display_name = "Alien Surgery" + description = "Abductors did nothing wrong." + prereq_ids = list("alientech", "surgery_tools") + design_ids = list( + "alien_cautery", + "alien_drill", + "alien_hemostat", + "alien_retractor", + "alien_saw", + "alien_scalpel", + "surgery_brainwashing", + "surgery_heal_combo_upgrade_femto", + "surgery_zombie", + "surgerytablev3", + ) + required_items_to_unlock = list( + /obj/item/abductor, + /obj/item/cautery/alien, + /obj/item/circuitboard/machine/abductor, + /obj/item/circular_saw/alien, + /obj/item/crowbar/abductor, + /obj/item/gun/energy/alien, + /obj/item/gun/energy/shrink_ray, + /obj/item/hemostat/alien, + /obj/item/melee/baton/abductor, + /obj/item/multitool/abductor, + /obj/item/retractor/alien, + /obj/item/scalpel/alien, + /obj/item/screwdriver/abductor, + /obj/item/surgicaldrill/alien, + /obj/item/weldingtool/abductor, + /obj/item/wirecutters/abductor, + /obj/item/wrench/abductor, + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) + discount_experiments = list(/datum/experiment/scanning/points/slime/hard = TECHWEB_TIER_5_POINTS) + hidden = TRUE + +/datum/design/surgerytablev3 + name = "Surgery table NERV" + id = "surgerytablev3" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*0.4, /datum/material/glass =SMALL_MATERIAL_AMOUNT*0.1) + build_path = /obj/item/circuitboard/machine/surgerytablev3 + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_CHEMISTRY, + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL diff --git a/modular_bandastation/medical/code/new_wounds/internal_bleed.dm b/modular_bandastation/medical/code/new_wounds/internal_bleed.dm new file mode 100644 index 0000000000000..95503b38ddfd6 --- /dev/null +++ b/modular_bandastation/medical/code/new_wounds/internal_bleed.dm @@ -0,0 +1,232 @@ + +/* + Internal wound bleed +*/ +/datum/wound/internal_bleed/bleed + name = "Internal Bleeding" + sound_effect = 'sound/weapons/slice.ogg' + processes = TRUE + treatable_tools = list(TOOL_HEMOSTAT) + base_treat_time = 5 SECONDS + wound_flags = MANGLES_INTERIOR + + /// How much blood we start losing when this wound is first applied + var/initial_flow + /// When hit on this bodypart, we have this chance of losing some blood + the incoming damage + var/internal_bleeding_chance + /// If we let off blood when hit, the max blood lost is this * the incoming damage + var/internal_bleeding_coefficient + /// Chance of heart attack due damage calculated by each tick + var/heart_attack_chance + /// Chance of lungs damage dealt by each tick + var/lungs_damage + +/datum/wound/internal_bleed/bleed/wound_injury(datum/wound/old_wound = null, attack_direction = null) + set_blood_flow(initial_flow) + if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY) + victim.spray_blood(attack_direction, severity) + + return ..() + +/datum/wound/internal_bleed/bleed/receive_damage(wounding_type, wounding_dmg, wound_bonus) + if(victim.stat == DEAD || (wounding_dmg < 5) || !limb.can_bleed() || !victim.blood_volume || !prob(internal_bleeding_chance + wounding_dmg)) + return + var/blood_bled = rand(1, wounding_dmg * internal_bleeding_coefficient) // 12 brute toolbox can cause up to 15/18/21 bloodloss on mod/sev/crit + switch(blood_bled) + if(1 to 6) + victim.bleed(blood_bled, TRUE) + if(7 to 13) + victim.visible_message("Blood droplets fly from [victim]'s mouth.", span_danger("You cough up a bit of blood from the blow."), vision_distance=COMBAT_MESSAGE_RANGE) + victim.bleed(blood_bled, TRUE) + if(14 to 19) + victim.visible_message("A small stream of blood spurts from [victim]'s mouth!", span_danger("You spit out a string of blood from the blow!"), vision_distance=COMBAT_MESSAGE_RANGE) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.bleed(blood_bled) + if(20 to INFINITY) + victim.visible_message(span_danger("A spray of blood streams from [victim]'s mouth!"), span_danger("You choke up on a spray of blood from the blow!"), vision_distance=COMBAT_MESSAGE_RANGE) + victim.bleed(blood_bled) + new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir) + victim.add_splatter_floor(get_step(victim.loc, victim.dir)) + +/datum/wound/internal_bleed/bleed/get_bleed_rate_of_change() + //basically if a species doesn't bleed, the wound is stagnant and will not heal on it's own (nor get worse) + if(!limb.can_bleed()) + return BLOOD_FLOW_STEADY + if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) + return BLOOD_FLOW_INCREASING + return BLOOD_FLOW_STEADY + +/datum/wound/internal_bleed/bleed/handle_process(seconds_per_tick, times_fired) + if (!victim || HAS_TRAIT(victim, TRAIT_STASIS)) + return + + set_blood_flow(min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)) + + if(limb.can_bleed()) + if(victim.bodytemperature < (BODYTEMP_NORMAL - 10)) + adjust_blood_flow(-0.1 * seconds_per_tick) + if(SPT_PROB(2.5, seconds_per_tick)) + to_chat(victim, span_notice("You feel the [LOWER_TEXT(name)] in your [limb.plaintext_zone] firming some sort of buldge!")) + + if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) + adjust_blood_flow(0.25 * seconds_per_tick) // old heparin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first + + var/obj/item/bodypart/chest/chest = victim.get_bodypart(BODY_ZONE_CHEST) + if (heart_attack_chance > 0 && limb == chest && rand(0,100) < heart_attack_chance) + var/datum/disease/heart_failure/heart_attack = new(src) + heart_attack.stage_prob = 5 //Advances twice as fast + victim.ForceContractDisease(heart_attack) + + var/obj/item/organ/internal/lungs/lungs = victim.get_organ_by_type(/obj/item/organ/internal/lungs) + if (lungs_damage > 0 && !isnull(lungs) && limb == chest) + lungs.apply_organ_damage(lungs_damage) + + + if(blood_flow <= 0) + qdel(src) + +/datum/wound/internal_bleed/bleed/on_stasis(seconds_per_tick, times_fired) + . = ..() + if(blood_flow <= 0) + qdel(src) + +/datum/wound/internal_bleed/bleed/treat(obj/item/I, mob/user) + if(I.tool_behaviour == TOOL_HEMOSTAT || I.get_temperature()) + return tool_clamping(I, user) + +/datum/wound/internal_bleed/bleed/on_xadone(power) + . = ..() + + if (limb) // parent can cause us to be removed, so its reasonable to check if we're still applied + adjust_blood_flow(-0.03 * power) // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort + +/// Полевое лечение +/datum/wound/internal_bleed/bleed/proc/tool_clamping(obj/item/I, mob/user) + + var/improv_penalty_mult = (I.tool_behaviour == TOOL_HEMOSTAT ? 1 : 1.25) // 25% longer and less effective if you don't use a real hemostat + var/self_penalty_mult = (user == victim ? 1.5 : 1) // 50% longer and less effective if you do it to yourself + var/pierce_founded = FALSE + var/treatment_delay = base_treat_time * self_penalty_mult * improv_penalty_mult + + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + treatment_delay *= 0.5 + user.visible_message(span_danger("[user] begins expertly restore internals in [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin restoring internals in [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image indications in mind...")) + else + user.visible_message(span_danger("[user] begins restoring internals in [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin restoring internals in [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + + for(var/limb_wound in limb.wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/pierce/bleed)) + pierce_founded = TRUE + + if (!(pierce_founded)) + return TRUE + + if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/bleeding_wording = (!limb.can_bleed() ? "holes" : "bleeding") + user.visible_message(span_green("[user] restore internals in [victim]."), span_green("You restore internals with reducing some of the [bleeding_wording] on [victim].")) + if(prob(30)) + victim.emote("scream") + var/blood_cauterized = (0.6 / (self_penalty_mult * improv_penalty_mult)) + adjust_blood_flow(-blood_cauterized) + + if(blood_flow > 0) + return try_treating(I, user) + return TRUE + +/datum/wound_pregen_data/flesh_internal_bleed + abstract = TRUE + + required_limb_biostate = (BIO_FLESH) + required_wounding_types = list(WOUND_BLUNT) + + wound_series = WOUND_SERIES_INTERNAL_BLEED + +/datum/wound/internal_bleed/get_limb_examine_description() + return span_warning("The flesh on this limb appears heavy buldged") + +/datum/wound/internal_bleed/bleed/moderate + name = "Minor internal bleeding" + desc = "Patient's skin has been formed some kind of bulge. Look a like there is a little intrnal bleed." + treat_text = "Treat affected site with cautarizing inside after piecring patient's skin." // space is cold in ss13, so it's like an ice pack! + examine_desc = "has a small bulge" + occur_text = "spurts out a thin stream of blood" + severity = WOUND_SEVERITY_MODERATE + initial_flow = 3.0 + internal_bleeding_chance = 50 + internal_bleeding_coefficient = 3.75 + threshold_penalty = 5 + heart_attack_chance = 0 + lungs_damage = 0 + status_effect_type = /datum/status_effect/wound/internal_bleed/moderate + + simple_treat_text = "Internal bleeding operation the wound will remove blood loss, help the wound close by itself quicker, and speed up the blood recovery period." + homemade_treat_text = "Cauterizing can help to close wounds inside, but you need some small hole to get there." + +/datum/wound_pregen_data/flesh_internal_bleed/light_ib + abstract = FALSE + + wound_path_to_generate = /datum/wound/internal_bleed/bleed/moderate + + threshold_minimum = 40 + +/datum/wound/internal_bleed/bleed/moderate/update_descriptions() + if(!limb.can_bleed()) + examine_desc = "has a small, circular hole" + occur_text = "splits a small hole open" + +/datum/wound/internal_bleed/bleed/severe + name = "Noticable internal bleeding" + desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability." + treat_text = "Repair internals under skin by cautery throught some kind of hole." + examine_desc = "is a noticable buldge under skin on their limb" + occur_text = "looses a violent spray of blood" + severity = WOUND_SEVERITY_SEVERE + initial_flow = 4.5 + internal_bleeding_chance = 100 + internal_bleeding_coefficient = 4.25 + threshold_penalty = 15 + heart_attack_chance = 0 + lungs_damage = 1 + status_effect_type = /datum/status_effect/wound/internal_bleed/severe + + simple_treat_text = "Internal bleeding operation the wound will remove blood loss, help the wound close by itself quicker, and speed up the blood recovery period." + homemade_treat_text = "Cauterizing can help to close wounds inside, but you need some small hole to get there." + +/datum/wound_pregen_data/flesh_internal_bleed/medium_ib + abstract = FALSE + + wound_path_to_generate = /datum/wound/internal_bleed/bleed/severe + + threshold_minimum = 55 + +/datum/wound/internal_bleed/bleed/severe/update_descriptions() + if(!limb.can_bleed()) + occur_text = "tears a hole open" + +/datum/wound/internal_bleed/bleed/critical + name = "Ruptured internals" + desc = "Patient's internal tissue and circulatory system is shredded, causing significant internal bleeding and damage to internal organs." + treat_text = "Surgical repair of internal bleeding throught puncture wound, followed by supervised resanguination." + examine_desc = "is forming big buldge under their skin and you hear some sorts of hoarseness" + occur_text = "blasts apart, sending chunks of viscera flying in all directions" + severity = WOUND_SEVERITY_CRITICAL + initial_flow = 6 + internal_bleeding_chance = 150 + internal_bleeding_coefficient = 4.75 + threshold_penalty = 25 + heart_attack_chance = 2 + lungs_damage = 5 + status_effect_type = /datum/status_effect/wound/internal_bleed/critical + + simple_treat_text = "Internal bleeding operation the wound will remove blood loss, help the wound close by itself quicker, and speed up the blood recovery period." + homemade_treat_text = "Cauterizing can help to close wounds inside, but you need some small hole to get there." + +/datum/wound_pregen_data/flesh_internal_bleed/heavy_ib + abstract = FALSE + + wound_path_to_generate = /datum/wound/internal_bleed/bleed/critical + + threshold_minimum = 70 diff --git a/modular_bandastation/medical/code/new_wounds/necrosis.dm b/modular_bandastation/medical/code/new_wounds/necrosis.dm new file mode 100644 index 0000000000000..0df60950b663b --- /dev/null +++ b/modular_bandastation/medical/code/new_wounds/necrosis.dm @@ -0,0 +1,247 @@ +/* + Necrosis wounds +*/ +/datum/wound/necrosis/basic_necro + name = "Cells necrosis" + sound_effect = 'sound/weapons/slice.ogg' + processes = TRUE + treatable_tools = list(TOOL_HEMOSTAT) + base_treat_time = 3 SECONDS + wound_flags = MANGLES_EXTERIOR + + var/necrosing_progress = 0 + var/necrosing_max + /// How fast progress necrosis to next stage + var/necrosing_coefficient + /// How many toxins damage dealt on each tock + var/toxin_damage_basic + /// How many organ damage dealt on each tock + var/organ_damage_basic + + +/datum/wound/necrosis/basic_necro/handle_process(seconds_per_tick, times_fired) + if (!victim || HAS_TRAIT(victim, TRAIT_STASIS)) + return + var/datum/wound/necro_wound + for(var/limb_wound in limb.wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/necrosis/basic_necro/)) + necro_wound = current_wound + + if(!(victim.has_status_effect(/datum/status_effect/necroinversite))) + //Если некроз на груди, голове или пахе - не более 2 уровня + if(victim.bodytemperature > (BODYTEMP_NORMAL - 10) && necrosing_progress <= (necrosing_max + 10)) + necrosing_progress += necrosing_coefficient * times_fired * 0.15 + else + if (!(istype(necro_wound, /datum/wound/necrosis/basic_necro/critical))) + necrosing_progress -= 3 + + if (necrosing_progress >= (necrosing_max) && (istype(necro_wound, /datum/wound/necrosis/basic_necro/moderate))) + var/wound_type = /datum/wound/necrosis/basic_necro/severe + var/datum/wound/necrosis/basic_necro/severe_wound = new wound_type() + severe_wound.apply_wound(limb,silent = TRUE,old_wound = necro_wound,wound_source = "Progressing infection",replacing = TRUE) + necro_wound.remove_wound() + if (necrosing_progress >= (necrosing_max) && (istype(necro_wound, /datum/wound/necrosis/basic_necro/severe))) + if(limb != victim.get_bodypart(BODY_ZONE_CHEST) && limb != victim.get_bodypart(BODY_ZONE_HEAD)) + var/wound_type = /datum/wound/necrosis/basic_necro/critical + var/datum/wound/necrosis/basic_necro/critical/crit_wound = new wound_type() + crit_wound.apply_wound(limb,silent = TRUE,old_wound = necro_wound,wound_source = "Progressing infection",replacing = TRUE) + necro_wound.remove_wound() + + if (prob(100) >= 50 && (istype(necro_wound, /datum/wound/necrosis/basic_necro/severe))) + if (!disabling) + to_chat(victim, span_warning("Your [limb.plaintext_zone] completely locks up, as you struggle for control against the infection!")) + set_disabling(TRUE) + else + to_chat(victim, span_notice("You regain sensation in your [limb.plaintext_zone], but it's still in terrible shape!")) + set_disabling(FALSE) + + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/severe)) + if (!disabling) + to_chat(victim, span_warning("Your [limb.plaintext_zone] completely locks up, as you struggle for control against the infection!")) + set_disabling(TRUE) + + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/critical)) + var/datum/gas_mixture/corpseGas = new + corpseGas.assert_gas(/datum/gas/miasma) + + var/toxin_damage = toxin_damage_basic * (necrosing_progress / 100) + var/organ_damage = organ_damage_basic * (necrosing_progress / 100) + + if (!(isnull(victim))) + victim.adjustToxLoss(toxin_damage) + if ((istype(necro_wound, /datum/wound/necrosis/basic_necro/severe) || (istype(necro_wound, /datum/wound/necrosis/basic_necro/critical)))) + for(var/obj/item/organ/internal/selected_organ as anything in victim.get_organs_for_zone(BODY_ZONE_CHEST,include_children = TRUE)) + if (organ_damage > 0 && !isnull(selected_organ)) + selected_organ.apply_organ_damage(organ_damage) + for(var/obj/item/organ/internal/selected_organ as anything in victim.get_organs_for_zone(BODY_ZONE_HEAD,include_children = TRUE)) + if (organ_damage > 0 && !isnull(selected_organ)) + selected_organ.apply_organ_damage(organ_damage) + + if (necrosing_progress < 5 && rand(1, 100) >= 20) + necrosing_progress = 0 + + if(necrosing_progress <= 0) + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/severe)) + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(limb,silent = TRUE,old_wound = necro_wound,wound_source = "Curing infection",replacing = TRUE) + moderate_wound.necrosing_progress = 99 + necro_wound.remove_wound() + to_chat(victim, span_notice("You regain sensation in your [limb.plaintext_zone], but it's still in terrible shape!")) + set_disabling(FALSE) + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/moderate)) + to_chat(victim, span_notice("You regain sensation in your [limb.plaintext_zone], but it's still in terrible shape!")) + set_disabling(FALSE) + qdel(src) + +/datum/wound/necrosis/basic_necro/on_stasis(seconds_per_tick, times_fired) + . = ..() + if(necrosing_progress <= 0) + var/datum/wound/necro_wound + for(var/limb_wound in limb.wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/necrosis/basic_necro/)) + necro_wound = current_wound + + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/severe)) + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(limb,silent = TRUE,old_wound = necro_wound,wound_source = "Curing infection",replacing = TRUE) + moderate_wound.necrosing_progress = 99 + necro_wound.remove_wound() + + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/moderate)) + qdel(src) + +/datum/wound/necrosis/basic_necro/treat(obj/item/I, mob/user) + if(I.tool_behaviour == TOOL_HEMOSTAT || I.get_temperature()) + return tool_clearing(I, user) + +/// Полевое лечение +/datum/wound/necrosis/basic_necro/proc/tool_clearing(obj/item/I, mob/user) + + var/datum/wound/necro_wound + for(var/limb_wound in limb.wounds) + var/datum/wound/current_wound = limb_wound + if(istype(current_wound, /datum/wound/necrosis/basic_necro/)) + necro_wound = current_wound + + if (istype(necro_wound, /datum/wound/necrosis/basic_necro/critical)) + return + + var/improv_penalty_mult = (I.tool_behaviour == TOOL_HEMOSTAT ? 1 : 1.25) // 25% longer and less effective if you don't use a real hemostat + var/self_penalty_mult = (user == victim ? 1.5 : 1) // 50% longer and less effective if you do it to yourself + var/treatment_delay = base_treat_time * self_penalty_mult * improv_penalty_mult + + if(HAS_TRAIT(src, TRAIT_WOUND_SCANNED)) + treatment_delay *= 0.5 + user.visible_message(span_danger("[user] begins expertly cleaning dead skin of [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cleaning dead skin of [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I], keeping the holo-image indications in mind...")) + else + user.visible_message(span_danger("[user] begins cleaning dead skin of [victim]'s [limb.plaintext_zone] with [I]..."), span_warning("You begin cleaning dead skin of [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [I]...")) + + if(!do_after(user, treatment_delay, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + return TRUE + + var/necro_wording = (!limb.can_bleed() ? "dead cells" : "patches of dead skin") + user.visible_message(span_green("[user] restore internals in [victim]."), span_green("You restore skin with reducing some of the [necro_wording] on [victim].")) + if(prob(30)) + victim.emote("scream") + + necrosing_progress = necrosing_progress - treatment_delay * 2 + + if(necrosing_progress > 0) + return try_treating(I, user) + return TRUE + +/datum/wound_pregen_data/flesh_necrosis + abstract = TRUE + + required_limb_biostate = (BIO_FLESH) + required_wounding_types = list(WOUND_PIERCE,WOUND_SLASH) + + wound_series = WOUND_SERIES_NECROSIS + +/datum/wound/necrosis/get_limb_examine_description() + return span_warning("The flesh on this limb appears heavy buldged") + +/datum/wound/necrosis/basic_necro/moderate + name = "Light infection" + desc = "Patient's skin has a strange color of skin in places." + treat_text = "This should be treated by spaceacelin, hemostate or necroinversit." // space is cold in ss13, so it's like an ice pack! + examine_desc = "has strange color of skin" + occur_text = "have a peeling skin" + severity = WOUND_SEVERITY_MODERATE + necrosing_coefficient = 0.1 + necrosing_max = 100 + toxin_damage_basic = 0 + organ_damage_basic = 0 + threshold_penalty = 5 + status_effect_type = /datum/status_effect/wound/necrosis/moderate + + simple_treat_text = "Spaceacelin, Necroinversit can remove the dying of cells process." + homemade_treat_text = "Hemostat can help to remove some patches of dead skin." + +/datum/wound_pregen_data/flesh_necrosis/light_necrosis + abstract = FALSE + + wound_path_to_generate = /datum/wound/necrosis/basic_necro/moderate + + threshold_minimum = 1 + +/datum/wound/necrosis/basic_necro/moderate/update_descriptions() + if(!limb.can_bleed()) + examine_desc = "has a small, circular hole" + occur_text = "splits a small hole open" + +/datum/wound/necrosis/basic_necro/severe + name = "Noticable dead skin" + desc = "Patient's have a weird gray color." + treat_text = "This should be treated by spaceacelin, hemostate or necroinversit." + examine_desc = "is a noticable flesh wound with falling skin" + occur_text = "looses a patch of their skin" + severity = WOUND_SEVERITY_SEVERE + necrosing_coefficient = 0.1 + necrosing_max = 150 + toxin_damage_basic = 0.25 + organ_damage_basic = 0.1 + threshold_penalty = 15 + status_effect_type = /datum/status_effect/wound/necrosis/severe + + simple_treat_text = "Necroinversit still can be helpfull to reverse the process of dying cells." + homemade_treat_text = "Debride Infected Flesh operation can help to remove some patches of dead skin cells to prevent futher processing of dying." + +/datum/wound_pregen_data/flesh_necrosis/medium_necrosis + abstract = FALSE + + wound_path_to_generate = /datum/wound/necrosis/basic_necro/severe + + threshold_minimum = 1000 + +/datum/wound/necrosis/basic_necro/severe/update_descriptions() + if(!limb.can_bleed()) + occur_text = "tears a hole open" + +/datum/wound/necrosis/basic_necro/critical + name = "Gangren" + desc = "Patient limb looks lifeless and rotten." + treat_text = "Surgical amputation of limb is only way to safe patient." + examine_desc = "is forming horrible wounds of open flesh that stinks" + occur_text = "have horrible smell of rotten and decaying flesh" + severity = WOUND_SEVERITY_CRITICAL + necrosing_coefficient = 1 + necrosing_max = 10 + toxin_damage_basic = 0.5 + organ_damage_basic = 0.25 + threshold_penalty = 25 + status_effect_type = /datum/status_effect/wound/necrosis/critical + + simple_treat_text = "Amputation is the only way to stop cruel afflictions of wound." + homemade_treat_text = "Nothing helpfull. The only way to stop wound to afflict pations is amputation." + +/datum/wound_pregen_data/flesh_necrosis/heavy_necrosis + abstract = FALSE + + wound_path_to_generate = /datum/wound/necrosis/basic_necro/critical + + threshold_minimum = 1000 diff --git a/modular_bandastation/medical/code/new_wounds/wounds_remake.dm b/modular_bandastation/medical/code/new_wounds/wounds_remake.dm new file mode 100644 index 0000000000000..b442b7507fbc2 --- /dev/null +++ b/modular_bandastation/medical/code/new_wounds/wounds_remake.dm @@ -0,0 +1,63 @@ +/datum/wound/burn/flesh/handle_process(seconds_per_tick, times_fired) + + if (!victim || HAS_TRAIT(victim, TRAIT_STASIS)) + return + + . = ..() + for(var/datum/reagent/reagent as anything in victim.reagents.reagent_list) + if(reagent.chemical_flags & REAGENT_AFFECTS_WOUNDS) + reagent.on_burn_wound_processing(src) + + if(HAS_TRAIT(victim, TRAIT_VIRUS_RESISTANCE)) + sanitization += 0.9 + + if(limb.current_gauze) + limb.seep_gauze(WOUND_BURN_SANITIZATION_RATE * seconds_per_tick) + + if(flesh_healing > 0) // good bandages multiply the length of flesh healing + var/bandage_factor = limb.current_gauze?.burn_cleanliness_bonus || 1 + flesh_damage = max(flesh_damage - (0.5 * seconds_per_tick), 0) + flesh_healing = max(flesh_healing - (0.5 * bandage_factor * seconds_per_tick), 0) // good bandages multiply the length of flesh healing + + // if we have little/no infection, the limb doesn't have much burn damage, and our nutrition is good, heal some flesh + if(infestation <= WOUND_INFECTION_MODERATE && (limb.burn_dam < 5) && (victim.nutrition >= NUTRITION_LEVEL_FED)) + flesh_healing += 0.2 + + // here's the check to see if we're cleared up + if((flesh_damage <= 0) && (infestation <= WOUND_INFECTION_MODERATE)) + to_chat(victim, span_green("The burns on your [limb.plaintext_zone] have cleared up!")) + qdel(src) + return + + infestation += infestation_rate * seconds_per_tick + switch(infestation) + if(0 to WOUND_INFECTION_MODERATE) + return + + if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE) + if(SPT_PROB(15, seconds_per_tick)) + victim.adjustToxLoss(0.2) + if(prob(6)) + to_chat(victim, span_warning("The blisters on your [limb.plaintext_zone] ooze a strange pus...")) + + if(WOUND_INFECTION_SEVERE to WOUND_INFECTION_CRITICAL) + if (prob(100) >= 66.6) + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(limb,silent = TRUE,wound_source = "Burning infection progress") + moderate_wound.necrosing_progress = 5 + return + + if(WOUND_INFECTION_CRITICAL to WOUND_INFECTION_SEPTIC) + if (prob(100) >= 33.3) + var/wound_type = /datum/wound/necrosis/basic_necro/moderate + var/datum/wound/necrosis/basic_necro/moderate_wound = new wound_type() + moderate_wound.apply_wound(limb,silent = TRUE,wound_source = "Burning infection progress") + moderate_wound.necrosing_progress = 5 + return + + if(WOUND_INFECTION_SEPTIC to INFINITY) + var/wound_type = /datum/wound/necrosis/basic_necro/severe + var/datum/wound/necrosis/basic_necro/severe_wound = new wound_type() + severe_wound.apply_wound(limb,silent = TRUE,wound_source = "Burning infection progress") + severe_wound.necrosing_progress = 5 diff --git a/modular_bandastation/medical/code/organ_damage/on_limb_damage.dm b/modular_bandastation/medical/code/organ_damage/on_limb_damage.dm new file mode 100644 index 0000000000000..b7b7dd9fea935 --- /dev/null +++ b/modular_bandastation/medical/code/organ_damage/on_limb_damage.dm @@ -0,0 +1,39 @@ +/obj/item/bodypart/receive_damage(brute = 0, burn = 0, blocked = 0, updating_health = TRUE, forced = FALSE, required_bodytype = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, damage_source) + . = .. () + + if(brute) + src.pain += brute * ADVMED_PAIN_APPLICATION_MODIFIER + if(burn) + src.pain += burn * (ADVMED_PAIN_APPLICATION_MODIFIER * 2) + + var/mob/living/carbon/human/afflicted = src.loc + var/damage = 0 + switch(src.body_zone) + if(BODY_ZONE_HEAD) + if(!HAS_TRAIT(src, TRAIT_HEAD_INJURY_BLOCKED)) + if(prob(brute)) + damage = brute * 0.5 + afflicted.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage, 200) + if(afflicted.stat == CONSCIOUS) + visible_message( + span_danger("[afflicted] is knocked senseless!"), + span_userdanger("You're knocked senseless!"), + ) + afflicted.set_confusion_if_lower(20 SECONDS) + afflicted.adjust_eye_blur(20 SECONDS) + if(prob(10)) + var/mob/living/carbon/affected_mob = new (src) + affected_mob.gain_trauma(/datum/brain_trauma/mild/concussion) + else + damage = brute * 0.2 + afflicted.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage, 200) + if(BODY_ZONE_CHEST) + if(prob(brute)) + damage = brute * 0.3 + afflicted.adjustOrganLoss(ORGAN_SLOT_STOMACH, damage, 200) + else if(prob(brute)) + damage = brute * 0.2 + afflicted.adjustOrganLoss(ORGAN_SLOT_LUNGS, damage, 200) + else if(prob(brute)) + damage = brute * 0.4 + afflicted.adjustOrganLoss(ORGAN_SLOT_LIVER, damage, 200) diff --git a/modular_bandastation/medical/code/organ_damage/organ_damage_ondamage.dm b/modular_bandastation/medical/code/organ_damage/organ_damage_ondamage.dm new file mode 100644 index 0000000000000..043247311a2ed --- /dev/null +++ b/modular_bandastation/medical/code/organ_damage/organ_damage_ondamage.dm @@ -0,0 +1,86 @@ +/mob/living/carbon/human/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker) + . = ..() + switch(hit_zone) + if(BODY_ZONE_HEAD) + if(.) + if(wear_mask) + wear_mask.add_mob_blood(src) + update_worn_mask() + if(head) + head.add_mob_blood(src) + update_worn_head() + if(glasses && prob(33)) + glasses.add_mob_blood(src) + update_worn_glasses() + + // rev deconversion through blunt trauma. + // this can be signalized to the rev datum + if(mind && stat == CONSCIOUS && src != attacker && prob(damage_done + ((100 - health) * 0.5))) + var/datum/antagonist/rev/rev = mind.has_antag_datum(/datum/antagonist/rev) + rev?.remove_revolutionary(attacker) + + if(BODY_ZONE_CHEST) + if(.) + if(wear_suit) + wear_suit.add_mob_blood(src) + update_worn_oversuit() + if(w_uniform) + w_uniform.add_mob_blood(src) + update_worn_undersuit() + + if(stat == CONSCIOUS && !attacking_item.get_sharpness() && !HAS_TRAIT(src, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED) && attacking_item.damtype == BRUTE) + if(prob(damage_done)) + visible_message( + span_danger("[src] is knocked down!"), + span_userdanger("You're knocked down!"), + ) + apply_effect(6 SECONDS, EFFECT_KNOCKDOWN, armor_block) + + // Triggers force say events + if(damage_done > 10 || (damage_done >= 5 && prob(33))) + force_say() + + +/datum/species/handle_environment_pressure(mob/living/carbon/human/H, datum/gas_mixture/environment, seconds_per_tick, times_fired) + var/pressure = environment.return_pressure() + var/adjusted_pressure = H.calculate_affecting_pressure(pressure) + + // Set alerts and apply damage based on the amount of pressure + switch(adjusted_pressure) + // Very high pressure, show an alert and take damage + if(HAZARD_HIGH_PRESSURE to INFINITY) + if(HAS_TRAIT(H, TRAIT_RESISTHIGHPRESSURE)) + H.clear_alert(ALERT_PRESSURE) + else + var/pressure_damage = min(((adjusted_pressure / HAZARD_HIGH_PRESSURE) - 1) * PRESSURE_DAMAGE_COEFFICIENT, MAX_HIGH_PRESSURE_DAMAGE) * H.physiology.pressure_mod * H.physiology.brute_mod * seconds_per_tick + H.adjustBruteLoss(pressure_damage, required_bodytype = BODYTYPE_ORGANIC) + H.adjustOrganLoss(ORGAN_SLOT_LUNGS, pressure_damage, required_organ_flag = ORGAN_ORGANIC) + H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/highpressure, 2) + + // High pressure, show an alert + if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE) + H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/highpressure, 1) + + // No pressure issues here clear pressure alerts + if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE) + H.clear_alert(ALERT_PRESSURE) + + // Low pressure here, show an alert + if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE) + // We have low pressure resit trait, clear alerts + if(HAS_TRAIT(H, TRAIT_RESISTLOWPRESSURE)) + H.clear_alert(ALERT_PRESSURE) + else + H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/lowpressure, 1) + + // Very low pressure, show an alert and take damage + else + // We have low pressure resit trait, clear alerts + if(HAS_TRAIT(H, TRAIT_RESISTLOWPRESSURE)) + H.clear_alert(ALERT_PRESSURE) + else + var/pressure_damage = LOW_PRESSURE_DAMAGE * H.physiology.pressure_mod * H.physiology.brute_mod * seconds_per_tick + H.adjustBruteLoss(pressure_damage, required_bodytype = BODYTYPE_ORGANIC) + if(!H.invalid_internals()) + H.adjustOrganLoss(ORGAN_SLOT_LUNGS, pressure_damage, required_organ_flag = ORGAN_ORGANIC) + H.throw_alert(ALERT_PRESSURE, /atom/movable/screen/alert/lowpressure, 2) diff --git a/modular_bandastation/medical/code/organ_damage/organ_damage_oxygen.dm b/modular_bandastation/medical/code/organ_damage/organ_damage_oxygen.dm new file mode 100644 index 0000000000000..e0c09116ec236 --- /dev/null +++ b/modular_bandastation/medical/code/organ_damage/organ_damage_oxygen.dm @@ -0,0 +1,14 @@ +/mob/living/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL, required_respiration_type = ALL) + if(!can_adjust_oxy_loss(amount, forced, required_biotype, required_respiration_type)) + return 0 + + . = oxyloss + oxyloss = clamp((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + . -= oxyloss + + adjustOrganLoss(ORGAN_SLOT_BRAIN, amount * 0.15, 200,required_organ_flag = ORGAN_ORGANIC) + + if(!.) // no change, no need to update + return FALSE + if(updating_health) + updatehealth() diff --git a/modular_bandastation/medical/code/organ_damage/organ_damage_toxin.dm b/modular_bandastation/medical/code/organ_damage/organ_damage_toxin.dm new file mode 100644 index 0000000000000..66ebb0771f012 --- /dev/null +++ b/modular_bandastation/medical/code/organ_damage/organ_damage_toxin.dm @@ -0,0 +1,29 @@ +/mob/living/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL) + if(!can_adjust_tox_loss(amount, forced, required_biotype)) + return 0 + + if(!forced && HAS_TRAIT(src, TRAIT_TOXINLOVER)) //damage becomes healing and healing becomes damage + amount = -amount + if(HAS_TRAIT(src, TRAIT_TOXIMMUNE)) //Prevents toxin damage, but not healing + amount = min(amount, 0) + if(blood_volume) + if(amount > 0) + blood_volume = max(blood_volume - (5 * amount), 0) + else + blood_volume = max(blood_volume - amount, 0) + + else if(!forced && HAS_TRAIT(src, TRAIT_TOXIMMUNE)) //Prevents toxin damage, but not healing + amount = min(amount, 0) + + . = toxloss + toxloss = clamp((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(prob(amount)) + adjustOrganLoss(ORGAN_SLOT_LIVER, amount * 0.2, 200,required_organ_flag = ORGAN_ORGANIC) + toxloss -= amount * 0.2 + . -= toxloss + + if(!.) // no change, no need to update + return FALSE + + if(updating_health) + updatehealth() diff --git a/modular_bandastation/medical/code/organ_damage/remake_organ_decay.dm b/modular_bandastation/medical/code/organ_damage/remake_organ_decay.dm new file mode 100644 index 0000000000000..ffb1f634f9eb6 --- /dev/null +++ b/modular_bandastation/medical/code/organ_damage/remake_organ_decay.dm @@ -0,0 +1,20 @@ +/mob/living/carbon/handle_organs(seconds_per_tick, times_fired) + if(stat == DEAD) + if(reagents && (reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 1) || reagents.has_reagent(/datum/reagent/cryostylane)) || has_status_effect(/datum/status_effect/cpred)) // No organ decay if the body contains formaldehyde. + return + for(var/obj/item/organ/internal/organ in organs) + // On-death is where organ decay is handled + if(organ?.owner) // organ + owner can be null due to reagent metabolization causing organ shuffling + organ.on_death(seconds_per_tick, times_fired) + // We need to re-check the stat every organ, as one of our others may have revived us + if(stat != DEAD) + break + return + + // NOTE: organs_slot is sorted by GLOB.organ_process_order on insertion + for(var/slot in organs_slot) + // We don't use get_organ_slot here because we know we have the organ we want, since we're iterating the list containing em already + // This code is hot enough that it's just not worth the time + var/obj/item/organ/internal/organ = organs_slot[slot] + if(organ?.owner) // This exist mostly because reagent metabolization can cause organ reshuffling + organ.on_life(seconds_per_tick, times_fired) diff --git a/modular_bandastation/medical/code/pain/overrides.dm b/modular_bandastation/medical/code/pain/overrides.dm new file mode 100644 index 0000000000000..43cea4fe98efc --- /dev/null +++ b/modular_bandastation/medical/code/pain/overrides.dm @@ -0,0 +1,32 @@ +/obj/item/organ/apply_organ_damage(damage_amount, maximum = maxHealth, required_organ_flag = NONE) //use for damaging effects + if(!damage_amount) //Micro-optimization. + return FALSE + maximum = clamp(maximum, 0, maxHealth) // the logical max is, our max + if(maximum < damage) + return FALSE + if(required_organ_flag && !(organ_flags & required_organ_flag)) + return FALSE + src.damage = clamp(src.damage + damage_amount, 0, maximum) + . = (src.prev_damage - src.damage) // return net damage + var/message = check_damage_thresholds(owner) + src.prev_damage = src.damage + + if(damage >= maxHealth) + organ_flags |= ORGAN_FAILING + else + organ_flags &= ~ORGAN_FAILING + + if(message && owner && owner.stat <= SOFT_CRIT) + to_chat(owner, message) + +/obj/item/bodypart + var/pain = 0 + +/obj/item/organ/internal + var/pain = 0 + +/mob/living/carbon/proc/adjustOrganScarring(slot) + var/obj/item/organ/affected_organ = get_organ_slot(slot) + if(!affected_organ || (status_flags & GODMODE)) + return FALSE + affected_organ.maxHealth = affected_organ.maxHealth - affected_organ.maxHealth * 0.5 diff --git a/modular_bandastation/medical/icons/kits.dmi b/modular_bandastation/medical/icons/kits.dmi new file mode 100644 index 0000000000000..37bf96fe56f03 Binary files /dev/null and b/modular_bandastation/medical/icons/kits.dmi differ diff --git a/modular_bandastation/medical/icons/medical_suits.dmi b/modular_bandastation/medical/icons/medical_suits.dmi new file mode 100644 index 0000000000000..b635e4efb4a2b Binary files /dev/null and b/modular_bandastation/medical/icons/medical_suits.dmi differ diff --git a/modular_bandastation/medical/icons/operation_table_v3.dmi b/modular_bandastation/medical/icons/operation_table_v3.dmi new file mode 100644 index 0000000000000..e244645f1e485 Binary files /dev/null and b/modular_bandastation/medical/icons/operation_table_v3.dmi differ diff --git a/modular_bandastation/medical/icons/surgery_table.dmi b/modular_bandastation/medical/icons/surgery_table.dmi new file mode 100644 index 0000000000000..c88dea217ace5 Binary files /dev/null and b/modular_bandastation/medical/icons/surgery_table.dmi differ diff --git a/modular_bandastation/modular_bandastation.dme b/modular_bandastation/modular_bandastation.dme index 36693b0faaacd..eb665d1d214f6 100644 --- a/modular_bandastation/modular_bandastation.dme +++ b/modular_bandastation/modular_bandastation.dme @@ -20,6 +20,7 @@ #include "examine_panel/_examine_panel.dme" #include "gunhud/_gunhud.dme" #include "keybinding/_keybinding.dme" +#include "medical/_medical.dme" #include "loadout/_loadout.dme" #include "mapping/_mapping.dme" #include "pixel_shift/_pixel_shift.dme"