/obj/item/organ name = "organ" icon = 'icons/obj/surgery.dmi' germ_level = 0 drop_sound = 'sound/items/drop/flesh.ogg' pickup_sound = 'sound/items/pickup/flesh.ogg' // Strings. var/organ_tag = "organ" // Unique identifier. var/parent_organ = BP_TORSO // Organ holding this object. // Status tracking. var/status = 0 // Various status flags var/vital // Lose a vital limb, die immediately. var/damage = 0 // Current damage to the organ var/robotic = 0 var/stapled_nerves = FALSE // Reference data. var/mob/living/carbon/human/owner // Current mob owning the organ. var/list/transplant_data // Transplant match data. var/list/autopsy_data = list() // Trauma data for forensics. var/list/trace_chemicals = list() // Traces of chemicals in the organ. var/datum/organ_data/data = new() // Stores data for appearance and investigation // Damage vars. var/min_bruised_damage = 10 // Damage before considered bruised var/min_broken_damage = 30 // Damage before becoming broken var/max_damage // Damage cap var/can_reject = 1 // Can this organ reject? var/rejecting // Is this organ already being rejected? var/decays = TRUE // Can this organ decay at all? var/preserved = 0 // If this is 1, prevents organ decay. // Language vars. Putting them here in case we decide to do something crazy with sign-or-other-nonverbal languages. var/list/will_assist_languages = list() var/list/datum/language/assists_languages = list() // Organ verb vars. var/list/organ_verbs // Verbs added by the organ when present in the body. var/list/target_parent_classes = list() // Is the parent supposed to be organic, robotic, assisted? var/forgiving_class = TRUE // Will the organ give its verbs when it isn't a perfect match? I.E., assisted in organic, synthetic in organic. var/butcherable = TRUE var/meat_type // What does butchering, if possible, make? var/list/medical_issues = list() ///Var for attack_self chain var/special_handling = FALSE /obj/item/organ/Destroy() handle_organ_mod_special(TRUE) if(owner) owner = null if(transplant_data) transplant_data.Cut() if(autopsy_data) autopsy_data.Cut() if(trace_chemicals) trace_chemicals.Cut() QDEL_NULL(data) return ..() /obj/item/organ/proc/update_health() return /obj/item/organ/Initialize(mapload, var/internal) . = ..() create_reagents(5) if(isliving(loc)) src.owner = loc src.w_class = max(src.w_class + mob_size_difference(owner.mob_size, MOB_MEDIUM), 1) //smaller mobs have smaller organs. if(internal) if(!LAZYLEN(owner.internal_organs)) owner.internal_organs = list() if(!LAZYLEN(owner.internal_organs_by_name)) owner.internal_organs_by_name = list() owner.internal_organs |= src owner.internal_organs_by_name[organ_tag] = src else if(!LAZYLEN(owner.organs)) owner.organs = list() if(!LAZYLEN(owner.organs_by_name)) owner.organs_by_name = list() owner.organs |= src owner.organs_by_name[organ_tag] = src if(!max_damage) max_damage = min_broken_damage * 2 if(iscarbon(owner)) var/mob/living/carbon/C = owner if(!C.species) data.setup_from_species(GLOB.all_species[SPECIES_HUMAN]) if(owner.dna) data.setup_from_dna(C.dna) data.setup_from_species(C.species) else log_runtime("[src] at [loc] spawned without a proper DNA.") var/mob/living/carbon/human/H = C if(istype(H)) if(internal) var/obj/item/organ/external/E = H.get_organ(parent_organ) if(E) if(E.internal_organs == null) E.internal_organs = list() E.internal_organs |= src if(data) add_blooddna_organ(data) else data.setup_from_species(GLOB.all_species["Human"]) handle_organ_mod_special() /obj/item/organ/proc/set_initial_meat() if(owner) if(!meat_type) if(owner.isSynthetic()) meat_type = /obj/item/stack/material/steel else if(ishuman(owner)) var/mob/living/carbon/human/H = owner meat_type = H?.species?.meat_type if(!meat_type) if(owner.meat_type) meat_type = owner.meat_type else meat_type = /obj/item/reagent_containers/food/snacks/meat /obj/item/organ/proc/set_dna(var/datum/dna/new_dna) if(new_dna) data.setup_from_dna(new_dna) forensic_data?.clear_blooddna() add_blooddna_organ(data) /obj/item/organ/proc/die() if(robotic < ORGAN_ROBOT) status |= ORGAN_DEAD damage = max_damage STOP_PROCESSING(SSobj, src) handle_organ_mod_special(TRUE) if(owner && vital) owner.can_defib = FALSE owner.death() /obj/item/organ/proc/adjust_germ_level(var/amount) // Unless you're setting germ level directly to 0, use this proc instead germ_level = CLAMP(germ_level + amount, 0, INFECTION_LEVEL_MAX) /obj/item/organ/process() if(loc != owner) owner = null //dead already, no need for more processing if(status & ORGAN_DEAD) return // Don't process if we're in a freezer, an MMI or a stasis bag.or a freezer or something I dunno if(istype(loc,/obj/item/mmi)) return if(preserved) return //check if we've hit max_damage if(damage >= max_damage) die() handle_organ_proc_special() for(var/datum/medical_issue/I in medical_issues) I.handle_effects() //Process infections if(robotic >= ORGAN_ROBOT || (istype(owner) && (owner.species && (owner.species.flags & (IS_PLANT | NO_INFECT))))) germ_level = 0 return if(!owner && reagents) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in reagents.reagent_list if(B && prob(40) && !isbelly(loc)) //VOREStation Edit reagents.remove_reagent(REAGENT_ID_BLOOD,0.1) blood_splatter(src,B,1) if(CONFIG_GET(flag/organs_decay) && decays) damage += rand(1,3) if(damage >= max_damage) damage = max_damage adjust_germ_level(1) //If something knocked a limb off, usually it'll have 100ish germs. This means you have ~30 minutes to get it back on before it becomes necrotic. if(germ_level >= INFECTION_LEVEL_THREE) die() else if(owner && owner?.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs //** Handle antibiotics and curing infections handle_antibiotics() handle_rejection() handle_germ_effects() /obj/item/organ/examine(mob/user) . = ..() //Descriptors for 'status of the limb' if(status & ORGAN_DEAD) //Can happen for other reasons than infection. . += span_bolddanger("The [name] is dead.") if(status & ORGAN_MUTATED) . += span_danger("The [name] is mutated and deformed.") if(status & ORGAN_BROKEN) . += span_danger("The [name] is broken.") //Descriptors for 'how infected is this organ' if(germ_level < INFECTION_LEVEL_ONE) return switch(germ_level) if(INFECTION_LEVEL_ONE to INFECTION_LEVEL_TWO - 1) . += span_warning("Signs of a minor infection are apparent.") if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_THREE - 1) . += span_boldwarning("Signs of a moderate infection are apparent.") if(INFECTION_LEVEL_THREE to INFINITY) . += span_bolddanger("Necrosis has set in.") /obj/item/organ/get_description_info(list/additional_information) if(!additional_information) additional_information = list() if(butcherable && meat_type) additional_information += "Can be butchered with use of any sharp and edged object." if(germ_level) additional_information += "Can be washed in a sink, shower, or sprayed with space cleaner to clean infection. This will not bring it back from death, however." if(status & ORGAN_DEAD) additional_information += "Can have five units of peridaxon applied to bring the organ back from death. This will not cure any infection, however." . = ..(additional_information) return . /obj/item/organ/get_description_antag() . = ..() if(butcherable && meat_type) . += "Can be butchered with use of any sharp and edged object, allowing for quick disposal of evidence." return . //A little wonky: internal organs stop calling this (they return early in process) when dead, but external ones cause further damage when dead /obj/item/organ/proc/handle_germ_effects() //** Handle the effects of infections if(robotic >= ORGAN_ROBOT) //Just in case! germ_level = 0 return 0 var/antibiotics = iscarbon(owner) ? owner.chem_effects[CE_ANTIBIOTIC] || 0 : 0 var/infection_damage = 0 /// Infection damage //If the organ is dead, for the sake of organs that may have died due to non-infection, we'll only do damage if they have at least L2 infection (built up below) //A dead organ is bad, so you start getting flooded with toxins faster. if((status & ORGAN_DEAD) && antibiotics < ANTIBIO_OD && germ_level >= INFECTION_LEVEL_TWO) infection_damage = CLAMP(round((germ_level - INFECTION_LEVEL_TWO)/1000), 0.25, 1) //Between 0.25 to 1 tox per tick. //Ideally, we want them to either: A. Reach Medical or B. have their organ die. Dying to toxins is lame. //With this math: Toxins goes up by 0.001 per 2 seconds, up to 0.1. This means 200 seconds to reach 0.1 toxins per tick (germ level is now 700). //Limb death happens at germ level 1000. This means another 600 seconds to reach there if untreated. //Your kidneys helps purge toxins if you have 10% or less of your maxhealth in toxins damage. This is RNG though. (See kidneys/handle_organ_proc_special) //So you COULD get really lucky and keep healing your toxins away until your limb dies, or you could get unlucky die to toxins first. //Nonetheless, this should give a much more reasonable window for treatment. else if(germ_level > INFECTION_LEVEL_TWO && antibiotics < ANTIBIO_OD) infection_damage = CLAMP(round((germ_level - INFECTION_LEVEL_TWO)/1000), 0, 0.1) if(infection_damage) owner.adjustToxLoss(infection_damage) if (germ_level > 0 && germ_level < INFECTION_LEVEL_ONE/2 && prob(30)) adjust_germ_level(-antibiotics) /// Germ Accumulation //Dead organs accumulate germs indefinitely if(status & ORGAN_DEAD) adjust_germ_level(1) //Half of level 1 is growing but harmless if (germ_level >= INFECTION_LEVEL_ONE/2) //aiming for germ level to go from ambient to INFECTION_LEVEL_TWO in an average of 15 minutes if(!antibiotics && prob(round(germ_level/6))) adjust_germ_level(1) //Level 1 qualifies for specific organ processing effects if(germ_level >= INFECTION_LEVEL_ONE) . = 1 //Organ qualifies for effect-specific processing //var/fever_temperature = (owner.species.heat_level_1 - owner.species.body_temperature - 5)* min(germ_level/INFECTION_LEVEL_TWO, 1) + owner.species.body_temperature //owner.bodytemperature += between(0, (fever_temperature - T20C)/BODYTEMP_COLD_DIVISOR + 1, fever_temperature - owner.bodytemperature) var/fever_temperature = owner?.species.heat_discomfort_level * 1.10 //Heat discomfort level plus 10% if(owner?.bodytemperature < fever_temperature) owner?.bodytemperature += min(0.2,(fever_temperature - owner?.bodytemperature) / 10) //Will usually climb by 0.2, else 10% of the difference if less //Level two qualifies for further processing effects if (germ_level >= INFECTION_LEVEL_TWO) . = 2 //Organ qualifies for effect-specific processing //No particular effect on the general 'organ' at 3 //Level three qualifies for significant growth and further effects if (germ_level >= INFECTION_LEVEL_THREE && antibiotics < ANTIBIO_OD) . = 3 //Organ qualifies for effect-specific processing adjust_germ_level(rand(5,10)) //Germ_level increases without overdose of antibiotics /obj/item/organ/proc/handle_rejection() // Process unsuitable transplants. TODO: consider some kind of // immunosuppressant that changes transplant data to make it match. if(data && can_reject) if(!rejecting) if(blood_incompatible(data.b_type, owner.dna.b_type, data.get_species_name(), owner.species.name)) //VOREStation Edit - Process species by name. rejecting = 1 else rejecting++ //Rejection severity increases over time. if(rejecting % 10 == 0) //Only fire every ten rejection ticks. switch(rejecting) if(1 to 50) adjust_germ_level(1) if(51 to 200) adjust_germ_level(rand(1,2)) if(201 to 500) adjust_germ_level(rand(2,3)) if(501 to INFINITY) adjust_germ_level(rand(3,5)) owner.reagents.add_reagent(REAGENT_ID_TOXIN, rand(1,2)) /obj/item/organ/proc/receive_chem(chemical as obj) return 0 /obj/item/organ/proc/remove_rejuv() qdel(src) /obj/item/organ/proc/rejuvenate(var/ignore_prosthetic_prefs) damage = 0 status = 0 germ_level = 0 if(owner) handle_organ_mod_special() if(!ignore_prosthetic_prefs && owner && owner.client && owner.client.prefs && owner.client.prefs.read_preference(/datum/preference/name/real_name) == owner.real_name) var/list/organ_data = owner.client.prefs.read_preference(/datum/preference/organ_data) var/status = organ_data?[organ_tag] if(status == FBP_ASSISTED) mechassist() else if(status == FBP_MECHANICAL) robotize() /obj/item/organ/proc/is_damaged() return damage > 0 /obj/item/organ/proc/is_bruised() return damage >= min_bruised_damage /obj/item/organ/proc/is_broken() return (damage >= min_broken_damage || (status & ORGAN_CUT_AWAY) || (status & ORGAN_BROKEN)) //Germs /obj/item/organ/proc/handle_antibiotics() if(istype(owner)) var/antibiotics = owner.chem_effects[CE_ANTIBIOTIC] || 0 if (!germ_level || antibiotics < ANTIBIO_NORM) return if (germ_level < INFECTION_LEVEL_ONE) germ_level = 0 //cure instantly else if (germ_level < INFECTION_LEVEL_TWO) adjust_germ_level(-antibiotics*4) //at germ_level < 500, this should cure the infection in a minute else if (germ_level < INFECTION_LEVEL_THREE) adjust_germ_level(-antibiotics*2) //at germ_level < 1000, this will cure the infection in 5 minutes else adjust_germ_level(-antibiotics) // You waited this long to get treated, you don't really deserve this organ //Adds autopsy data for used_weapon. /obj/item/organ/proc/add_autopsy_data(var/used_weapon, var/damage) var/datum/autopsy_data/W = autopsy_data[used_weapon] if(!W) W = new() W.weapon = used_weapon autopsy_data[used_weapon] = W W.hits += 1 W.damage += damage W.time_inflicted = world.time //Note: external organs have their own version of this proc /obj/item/organ/take_damage(amount, var/silent=0) if(owner) if(SEND_SIGNAL(owner, COMSIG_INTERNAL_ORGAN_PRE_DAMAGE_APPLICATION, amount, silent) & COMPONENT_CANCEL_INTERNAL_ORGAN_DAMAGE) return 0 if(src.robotic >= ORGAN_ROBOT) src.damage = between(0, src.damage + (amount * 0.8), max_damage) else src.damage = between(0, src.damage + amount, max_damage) //only show this if the organ is not robotic if(owner && parent_organ && amount > 0) var/obj/item/organ/external/parent = owner?.get_organ(parent_organ) if(parent && !silent) owner.custom_pain("Something inside your [parent.name] hurts a lot.", amount) if(owner) SEND_SIGNAL(owner, COMSIG_INTERNAL_ORGAN_PRE_DAMAGE_APPLICATION, amount, silent) /obj/item/organ/proc/bruise() damage = max(damage, min_bruised_damage) /obj/item/organ/proc/break_organ() //can't name this break because it's a reserved word damage = max(damage, min_broken_damage) /obj/item/organ/proc/robotize() //Being used to make robutt hearts, etc robotic = ORGAN_ROBOT src.status &= ~ORGAN_BROKEN src.status &= ~ORGAN_BLEEDING src.status &= ~ORGAN_CUT_AWAY /obj/item/organ/proc/mechassist() //Used to add things like pacemakers, etc robotize() robotic = ORGAN_ASSISTED min_bruised_damage = 15 min_broken_damage = 35 butcherable = FALSE /obj/item/organ/proc/digitize() //Used to make the circuit-brain. On this level in the event more circuit-organs are added/tweaks are wanted. robotize() /obj/item/organ/emp_act(severity, recursive) for(var/obj/O as anything in src.contents) O.emp_act(severity, recursive) if(!(robotic >= ORGAN_ASSISTED)) return for(var/i = 1; i <= robotic; i++) switch (severity) if (1) take_damage(rand(5,9)) if (2) take_damage(rand(3,7)) if (3) take_damage(rand(2,5)) if (4) take_damage(rand(1,3)) /obj/item/organ/proc/removed(var/mob/living/user) if(owner) owner.internal_organs_by_name[organ_tag] = null owner.internal_organs_by_name -= organ_tag owner.internal_organs_by_name -= null owner.internal_organs -= src var/obj/item/organ/external/affected = owner.get_organ(parent_organ) if(affected) affected.internal_organs -= src owner.remove_from_mob(src, owner.drop_location()) START_PROCESSING(SSobj, src) rejecting = null if(istype(owner)) // VOREstation edit begin - Posibrains don't have blood reagents, so they crash this var/datum/reagent/blood/organ_blood = null if(reagents) organ_blood = locate(/datum/reagent/blood) in reagents.reagent_list // VOREstation edit end if(!organ_blood || !organ_blood.data["blood_DNA"]) owner.vessel.trans_to(src, 5, 1, 1) if(owner && vital) if(user) add_attack_logs(user, owner, "Removed vital organ [src.name]") if(owner.stat != DEAD) owner.can_defib = 0 owner.death() handle_organ_mod_special(TRUE) owner = null /obj/item/organ/proc/replaced(var/mob/living/carbon/human/target,var/obj/item/organ/external/affected) if(!istype(target)) return var/datum/reagent/blood/transplant_blood = null if(reagents) transplant_blood = locate(/datum/reagent/blood) in reagents.reagent_list transplant_data = list() if(!transplant_blood) transplant_data["species"] = target?.species.name transplant_data["blood_type"] = target?.dna.b_type transplant_data["blood_DNA"] = target?.dna.unique_enzymes else transplant_data["species"] = transplant_blood?.data["species"] transplant_data["blood_type"] = transplant_blood?.data["blood_type"] transplant_data["blood_DNA"] = transplant_blood?.data["blood_DNA"] owner = target loc = owner STOP_PROCESSING(SSobj, src) target.internal_organs |= src affected.internal_organs |= src target.internal_organs_by_name[organ_tag] = src handle_organ_mod_special() /obj/item/organ/proc/bitten(mob/user) if(robotic >= ORGAN_ROBOT) return to_chat(user, span_notice("You take an experimental bite out of \the [src].")) var/datum/reagent/blood/B = locate(/datum/reagent/blood) in reagents.reagent_list blood_splatter(src,B,1) user.drop_from_inventory(src) var/obj/item/reagent_containers/food/snacks/organ/O = new(get_turf(src)) O.name = name O.icon = icon O.icon_state = icon_state // Pass over the blood. reagents.trans_to(O, reagents.total_volume) transfer_fingerprints_to(O) transfer_blooddna_to(O) user.put_in_active_hand(O) qdel(src) /obj/item/organ/attack_self(mob/user, callback) . = ..(user) if(.) return TRUE if(special_handling && !callback) return FALSE // Convert it to an edible form, yum yum. if(!(robotic >= ORGAN_ROBOT) && user.a_intent == I_HELP && user.zone_sel.selecting == O_MOUTH) bitten(user) return /obj/item/organ/attackby(obj/item/W, mob/user) if(can_butcher(W, user)) butcher(W, user) return var/obj/item/reagent_containers/container = W if(istype(container)) if(container.reagents.has_reagent(REAGENT_ID_PERIDAXON, 5)) status &= ~ORGAN_DEAD damage-- //Fix JUST enough damage so it doesn't immediately die again. For full repair, use denec removal surgery. START_PROCESSING(SSobj, src) //When an organ dies, it stops processing. This restarts it. container.reagents.remove_reagent(REAGENT_ID_PERIDAXON, 5) to_chat(user, "You use the [container] to revive \the [src]") return return ..() /obj/item/organ/proc/can_butcher(var/obj/item/O, var/mob/living/user) if(butcherable && meat_type) if(istype(O, /obj/machinery/gibber)) // The great equalizer. return TRUE if(robotic >= ORGAN_ROBOT) if(O.has_tool_quality(TOOL_SCREWDRIVER)) return TRUE else if(is_sharp(O) && has_edge(O)) return TRUE return FALSE /obj/item/organ/proc/butcher(var/obj/item/O, var/mob/living/user, var/atom/newtarget) if(user) to_chat(user, span_danger("You are preparing to butcher \the [src]!")) user.visible_message(span_danger("[user] prepares to butcher \the [src]!")) if(!do_after(user, 10 SECONDS * O.toolspeed, target = src)) //They can queue this up on multiple organs. to_chat(user, span_notice("You reconsider butchering \the [src]...")) user.visible_message(span_notice("[user] reconsiders butchering \the [src]!")) return FALSE if(robotic >= ORGAN_ROBOT) user?.visible_message(span_warning("[user] disassembles \the [src].")) else user?.visible_message(span_warning("[user] butchers \the [src].")) if(!newtarget) newtarget = get_turf(src) var/obj/item/newmeat = new meat_type(newtarget) if(istype(newmeat, /obj/item/reagent_containers/food/snacks/meat)) newmeat.name = "[src.name] [newmeat.name]" // "liver meat" "heart meat", etc. if(LAZYLEN(contents)) //You can't shove the nuke disk into a leg and then butcher the leg to delete the nuke disk. for(var/obj/contained_object in contents) contained_object.forceMove(newtarget) qdel(src) /obj/item/organ/proc/organ_can_feel_pain() if(data.get_species_flags() & NO_PAIN) return 0 if(status & ORGAN_DESTROYED) return 0 if(robotic && robotic < ORGAN_LIFELIKE) //Super fancy humanlike robotics probably have sensors, or something? return 0 if(stapled_nerves) return 0 return 1 /obj/item/organ/proc/handle_organ_mod_special(var/removed = FALSE) // Called when created, transplanted, and removed. if(!istype(owner)) return var/list/save_verbs = list() if(removed && organ_verbs) // Do we share verbs with any other organs? Are they functioning? var/list/all_organs = list() all_organs |= owner.organs all_organs |= owner.internal_organs for(var/obj/item/organ/O in all_organs) if(!(O.status & ORGAN_DEAD) && O.organ_verbs && O.check_verb_compatability()) for(var/verb_type in O.organ_verbs) if(verb_type in organ_verbs) save_verbs |= verb_type if(!removed && organ_verbs && check_verb_compatability()) for(var/verb_path in organ_verbs) add_verb(owner, verb_path) else if(organ_verbs) for(var/verb_path in organ_verbs) if(!(verb_path in save_verbs)) remove_verb(owner, verb_path) return /obj/item/organ/proc/handle_organ_proc_special() // Called when processed. return /obj/item/organ/proc/check_verb_compatability() // Used for determining if an organ should give or remove its verbs. I.E., FBP part in a human, no verbs. If true, keep or add. if(owner) if(ishuman(owner)) var/mob/living/carbon/human/H = owner var/obj/item/organ/O = H.get_organ(parent_organ) if(forgiving_class) if(O.robotic <= ORGAN_ASSISTED && robotic <= ORGAN_LIFELIKE) // Parent is organic or assisted, we are at most synthetic. return TRUE if(O.robotic >= ORGAN_ROBOT && robotic >= ORGAN_ASSISTED) // Parent is synthetic, and we are biosynthetic at least. return TRUE if(!target_parent_classes || !target_parent_classes.len) // Default checks, if we're not looking for a Specific type. if(O.robotic == robotic) // Same thing, we're fine. return TRUE if(O.robotic < ORGAN_ROBOT && robotic < ORGAN_ROBOT) return TRUE if(O.robotic > ORGAN_ASSISTED && robotic > ORGAN_ASSISTED) return TRUE else if(O.robotic in target_parent_classes) return TRUE return FALSE