/**************************************************** EXTERNAL ORGANS ****************************************************/ //These control the damage thresholds for the various ways of removing limbs #define DROPLIMB_THRESHOLD_EDGE 5 #define DROPLIMB_THRESHOLD_TEAROFF 2 #define DROPLIMB_THRESHOLD_DESTROY 1 #define DROPLIMB_THRESHOLD_DESTROY_PROJECTILE 2 #define FRACTURE_AND_TENDON_DAM_THRESHOLD 30 // if a weapon does more than this amount of damage, it's powerful enough to sever the tendon AND fracture the bone, if it's a sharp weapon /obj/item/organ/external name = "external" min_broken_damage = 30 max_damage = 0 dir = SOUTH organ_tag = "limb" var/force_prosthetic_name var/icon_name = null var/body_part = null var/icon_position = 0 var/damage_state = "00" //Damage variables. Do not modify brute_dam or burn_dam directly. Use take_damage. ///Actual current brute damage var/brute_dam = 0 ///Actual current burn damage var/burn_dam = 0 ///Ratio of current brute damage to max damage var/brute_ratio = 0 ///Ratio of current burn damage to max damage var/burn_ratio = 0 /// Brute damage modifier. var/brute_mod = 1 /// Burn damage modifier. var/burn_mod = 1 var/last_dam = -1 ///Amount of current genetic damage var/genetic_degradation = 0 ///How much the limb hurts VAR_PRIVATE/pain = 0 ///The amount of `pain` at which a limb becomes unusable var/pain_disability_threshold ///Organ behaviour flags, see `ORGAN_CAN_*` and `ORGAN_HAS_*` in `code\__DEFINES\damage_organs.dm` var/limb_flags = ORGAN_CAN_AMPUTATE | ORGAN_CAN_BREAK | ORGAN_CAN_MAIM var/max_size = 0 ///The `/icon` of the mob that has this organ var/icon/mob_icon ///A list of overlays for the organ var/list/mutable_appearance/mob_overlays var/gendered_icon = 0 var/force_icon /// If set, will use this as the robotized force icon instead of the robotype var/override_robotize_force_icon /// If set, will use tihs as the painted value instead of the robotype var/override_robotize_painted /// Will robotize the children of this limb if set to true var/robotize_children = TRUE var/limb_name var/disfigured = 0 var/s_tone var/skin_color var/hair_color /// A `/list` of wounds var/list/wounds /// A list of implants present in this organ var/list/implants = list() /// Cache the number of wounds, which is NOT wounds.len! var/number_wounds = 0 var/perma_injury = 0 /// The parent organ var/obj/item/organ/external/parent /// A `/list` of organs that have this organ as a parent var/list/obj/item/organ/external/children /// Boolean, if this organ supports childrens, which will be added in `children` var/supports_children = TRUE /// Internal organs of this body part var/list/obj/item/organ/internal/internal_organs = list() /// The tendon var/datum/tendon/tendon /// A path of the type of tendon to create when this organ is created var/tendon_path = /datum/tendon /// Name of the limb's tendon. Achilles heel, etc. var/tendon_name = "tendon" /// HP value of the limb's tendon var/tendon_health = 30 var/list/tendon_msgs = list("tore apart", "ripped away") var/damage_msg = SPAN_WARNING("You feel an intense pain!") var/broken_description var/open = 0 var/stage = 0 var/cavity = 0 /// Boolean, if it was sabotaged (emagged), make it detonate when it fails var/sabotaged = FALSE /// Needs to be opened with a saw to access the organs var/encased /// Descriptive string used in dislocation var/joint = "joint" /// Name of the artery. Cartoid, etc. var/artery_name = "artery" /// Multiplier for bleeding in a limb var/arterial_bleed_severity = 0.75 /// Descriptive string used in amputation var/amputation_point /// If the joint is dislocated var/dislocated = FALSE /// How often wounds should be updated, a higher number means less often var/wound_update_accuracy = 1 var/body_hair var/painted = 0 /// The amount of bandages on our sprite var/bandage_level = BANDAGE_LEVEL_NONE /// For special projectile gibbing calculation, dubbed "maiming" var/maim_bonus = 0 /// Markings (body_markings) to apply to the icon var/list/genetic_markings /// Same as `genetic_markings`, but not preserved when cloning var/list/temporary_markings /// The `genetic_markings` and `temporary_markings` cached for perf. reasons var/list/cached_markings var/list/image/additional_images /// Pressure applied to wounds. It'll make them bleed less, generally. var/atom/movable/applied_pressure var/image/hud_damage_image /// How many augments you can fit inside this limb var/augment_limit var/obj/item/organ/internal/infect_target_internal //make internal organs become infected one at a time instead of all at once var/obj/item/organ/external/infect_target_external //make child and parent organs become infected one at a time instead of all at once /// Dionae limb var/mob/living/carbon/alien/diona/nymph var/nymph_child /// Whether the limb has an emissive icon state associated with it var/is_emissive = FALSE /// Whether the limb has an active overlay icon state associated with it var/is_overlay = FALSE /// Whether the limb is a tesla limb (required for special handling) var/is_tesla = FALSE /obj/item/organ/external/Initialize(mapload) if(robotize_type) robotize(robotize_type) drop_sound = 'sound/items/drop/prosthetic.ogg' pickup_sound = 'sound/items/pickup/prosthetic.ogg' else //HACK: Make sure non-emissive organs, if swapped to, are not treated as emissive. Robotized organs have their own handling, but we still need to cover the case where it is somehow swapped to an organic limb is_emissive = initial(is_emissive) . = ..(mapload, FALSE) if(isnull(pain_disability_threshold)) pain_disability_threshold = (max_damage * 0.75) if(owner) replaced(owner) sync_colour_to_human(owner) if ((status & ORGAN_PLANT)) limb_flags &= ~ORGAN_CAN_BREAK get_icon() RegisterSignal(src, COMSIG_UPDATE_LIMB_IMAGE, PROC_REF(modify_damage_hud_image_color)) get_damage_hud_image(TRUE) if((limb_flags & ORGAN_HAS_TENDON) && !BP_IS_ROBOTIC(src) && tendon_path) tendon = new tendon_path(src, tendon_name, tendon_health, tendon_msgs) else if(limb_flags & ORGAN_HAS_TENDON) limb_flags &= ~ORGAN_HAS_TENDON /obj/item/organ/external/Destroy() . = ..() if(parent?.children) parent.children -= src if(istype(owner) && owner.bad_external_organs) owner.bad_external_organs -= src if(limb_name && istype(owner, /mob/living/carbon) && owner.organs_by_name) owner.organs_by_name[limb_name] = null owner.organs_by_name -= limb_name parent = null //Remove the images from the owner and clear them if(istype(owner)) cut_additional_images(owner) additional_images = null genetic_markings = null temporary_markings = null cached_markings = null mob_icon = null for(var/datum/wound/wound in wounds) qdel(wound) QDEL_LIST(children) QDEL_LIST(internal_organs) QDEL_LIST(implants) infect_target_internal = null infect_target_external = null applied_pressure = null QDEL_NULL(nymph) QDEL_NULL(tendon) /obj/item/organ/external/proc/invalidate_marking_cache() cached_markings = null ///If the organ requires amputation, returns TRUE. Otherwise, returns FALSE /obj/item/organ/external/proc/CheckNeedsAmputation() var/extreme_damage = FALSE if(!(brute_ratio < 100)) extreme_damage = TRUE if(!(burn_ratio < 100)) extreme_damage = TRUE if(extreme_damage && !(limb_flags & ORGAN_HEALS_OVERKILL)) return TRUE // Limb took too much damage, nothing left to heal return FALSE /obj/item/organ/external/attack_self(var/mob/user) if(!length(contents)) return ..() var/list/removable_objects = list() for(var/obj/item/organ/external/E in (contents + src)) if(!istype(E)) continue for(var/obj/item/I in E.contents) if(istype(I,/obj/item/organ)) continue removable_objects |= I if(length(removable_objects)) var/obj/item/I = pick(removable_objects) I.forceMove(get_turf(user)) //just in case something was embedded that is not an item if(istype(I)) if(!(user.l_hand && user.r_hand)) user.put_in_hands(I) user.visible_message(SPAN_DANGER("\The [user] rips \the [I] out of \the [src]!")) return //no eating the limb until everything's been removed return ..() /obj/item/organ/external/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) . = ..() if(distance <= 1) for(var/obj/item/I in contents) if(istype(I, /obj/item/organ)) continue . += SPAN_DANGER("There is \a [I] sticking out of it.") /obj/item/organ/external/attackby(obj/item/attacking_item, mob/user) switch(stage) if(0) if(istype(attacking_item, /obj/item/surgery/scalpel)) user.visible_message(SPAN_DANGER("[user] cuts [src] open with [attacking_item]!")) stage++ return if(1) if(istype(attacking_item, /obj/item/surgery/retractor)) user.visible_message(SPAN_DANGER("[user] cracks [src] open like an egg with [attacking_item]!")) stage++ return if(2) if(istype(attacking_item, /obj/item/surgery/hemostat)) var/list/organs = get_contents_recursive() if(length(organs)) var/obj/item/removing = pick(organs) var/obj/item/organ/external/current_child = removing.loc current_child.implants.Remove(removing) current_child.internal_organs.Remove(removing) status |= ORGAN_CUT_AWAY removing.forceMove(get_turf(user)) if(!(user.l_hand && user.r_hand)) user.put_in_hands(removing) user.visible_message(SPAN_DANGER("[user] extracts [removing] from [src] with [attacking_item]!")) else user.visible_message(SPAN_DANGER("[user] fishes around fruitlessly in [src] with [attacking_item].")) return ..() /** * Get a list of contents of this organ and all the child organs */ /obj/item/organ/external/proc/get_contents_recursive() var/list/all_items = list() all_items.Add(implants) all_items.Add(internal_organs) for(var/obj/item/organ/external/child in children) all_items.Add(child.get_contents_recursive()) return all_items /obj/item/organ/external/proc/dislocate(var/primary) if(dislocated == -1) return if(primary) dislocated = 2 else dislocated = 1 add_verb(owner, /mob/living/carbon/human/proc/undislocate) if(children && children.len) for(var/obj/item/organ/external/child in children) child.dislocate() /obj/item/organ/external/proc/undislocate() if(dislocated == -1) return dislocated = 0 if(children && children.len) for(var/obj/item/organ/external/child in children) if(child.dislocated == 1) child.undislocate() if(owner) owner.shock_stage += 20 for(var/obj/item/organ/external/limb in owner.organs) if(limb.dislocated == 2) return remove_verb(owner, /mob/living/carbon/human/proc/undislocate) /obj/item/organ/external/update_health() damage = min(max_damage, (brute_dam + burn_dam)) return /obj/item/organ/external/replaced(var/mob/living/carbon/human/target) ..() if(istype(owner)) owner.organs_by_name[limb_name] = src owner.organs |= src if(!species) species = owner.species for(var/obj/item/organ/organ in internal_organs) organ.replaced(owner,src) for(var/obj/implant in implants) implant.forceMove(owner) if(istype(implant, /obj/item/implant)) var/obj/item/implant/imp_device = implant // we can't use implanted() here since it's often interactive imp_device.imp_in = owner imp_device.implanted = 1 for(var/obj/item/organ/external/organ in children) organ.replaced(owner) if(!parent && parent_organ) parent = owner.organs_by_name[src.parent_organ] if(parent) if(!parent.children) parent.children = list() parent.children.Add(src) //Remove all stump wounds since limb is not missing anymore for(var/datum/wound/lost_limb/W in parent.wounds) qdel(W) break parent.update_damages() action_button_name = initial(action_button_name) owner.update_action_buttons() /**************************************************** DAMAGE PROCS ****************************************************/ /obj/item/organ/external/proc/is_damageable(var/additional_damage = 0) //Continued damage to vital organs can kill you, and robot organs don't count towards total damage so no need to cap them. return (BP_IS_ROBOTIC(src) || brute_dam + burn_dam + additional_damage < max_damage * 4) /obj/item/organ/external/take_damage(brute, burn, damage_flags, used_weapon = null, list/forbidden_limbs = list(), var/silent) brute = round(brute * brute_mod, 0.1) burn = round(burn * burn_mod, 0.1) if((brute <= 0) && (burn <= 0)) return 0 var/laser = (damage_flags & DAMAGE_FLAG_LASER) var/sharp = (damage_flags & DAMAGE_FLAG_SHARP) var/edge = (damage_flags & DAMAGE_FLAG_EDGE) var/psionic = (damage_flags & DAMAGE_FLAG_PSIONIC) var/blunt = !!(brute && !sharp && !edge) /// Psionics and psionically deaf species take varying amounts of damage from psionic abilities. if(psionic) // Damage modifier is 0.9 for no psionics at all, 1 for baseline psychic, and an extra 0.1 per equivalent rank above baseline. var/psi_damage_modifier = 1 + (0.1 * (owner.check_psi_sensitivity() - 1)) brute *= psi_damage_modifier burn *= psi_damage_modifier if(status & ORGAN_BROKEN && prob(40) && brute) if(owner && (owner.can_feel_pain()) && owner.stat == CONSCIOUS) owner.emote(pick("scream", "groan")) //getting hit on broken hand hurts if(used_weapon) add_autopsy_data("[used_weapon]", brute + burn) var/spillover = 0 if(!is_damageable(brute + burn)) spillover = brute_dam + burn_dam + brute - max_damage if(spillover > 0) brute = max(brute - spillover, 0) else spillover = brute_dam + burn_dam + brute + burn - max_damage if(spillover > 0) burn = max(burn - spillover, 0) handle_limb_gibbing(used_weapon, brute, burn) if(brute_dam + brute > min_broken_damage && prob(brute_dam + brute * (1 + blunt))) if(blunt || brute > FRACTURE_AND_TENDON_DAM_THRESHOLD) fracture() // High brute damage or sharp objects may damage internal organs if(length(internal_organs)) if(damage_internal_organs(brute, burn, damage_flags)) brute /= 2 burn /= 2 var/datum/wound/created_wound var/can_cut = !BP_IS_ROBOTIC(src) && (sharp || prob(brute)) if(brute) var/to_create = INJURY_TYPE_BRUISE if(can_cut) to_create = INJURY_TYPE_CUT //need to check sharp again here so that blunt damage that was strong enough to break skin doesn't give puncture wounds if(sharp && !edge) to_create = INJURY_TYPE_PIERCE created_wound = createwound(to_create, brute) if(burn) if(laser) created_wound = createwound(INJURY_TYPE_LASER, burn) else created_wound = createwound(INJURY_TYPE_BURN, burn) add_pain(0.6 * burn + 0.4 * brute) if(owner) SEND_SIGNAL(owner, COMSIG_EXTERNAL_ORGAN_DAMAGE, burn + brute) //If there are still hurties to dispense if (spillover) owner.shock_stage += spillover * GLOB.config.organ_damage_spillover_multiplier // sync the organ's damage with its wounds update_damages() if(owner) owner.updatehealth() //droplimb will call updatehealth() again if it does end up being called update_icon() return created_wound /obj/item/organ/external/proc/damage_internal_organs(brute, burn, damage_flags) if(!length(internal_organs)) return FALSE var/damage_amt = brute var/cur_damage = brute_dam var/sharp = (damage_flags & DAMAGE_FLAG_SHARP) var/laser = (damage_flags & DAMAGE_FLAG_LASER) if(BP_IS_ROBOTIC(src) || laser) damage_amt += burn cur_damage += burn_dam if(!damage_amt) return FALSE var/organ_damage_threshold = 10 if(sharp) organ_damage_threshold *= 0.5 if(laser) organ_damage_threshold *= 1.25 if(!(cur_damage + damage_amt >= max_damage) && !(damage_amt >= organ_damage_threshold)) return FALSE var/list/victims = list() var/organ_hit_chance = 0 for(var/obj/item/organ/internal/I in internal_organs) if(I.damage < I.max_damage) victims[I] = I.relative_size organ_hit_chance += I.relative_size //No damageable organs if(!length(victims)) return FALSE organ_hit_chance += 5 * damage_amt / organ_damage_threshold if(!BP_IS_ROBOTIC(src)) if(encased && !(status & ORGAN_BROKEN)) //ribs protect organ_hit_chance *= 0.2 else organ_hit_chance *= 0.8 // robots should not have the same advantage organ_hit_chance = min(organ_hit_chance, 100) if(prob(organ_hit_chance)) var/obj/item/organ/internal/victim = pickweight(victims) damage_amt -= max(damage_amt*victim.damage_reduction, 0) victim.take_internal_damage(damage_amt) return TRUE /obj/item/organ/external/proc/handle_limb_gibbing(var/used_weapon, var/brute, var/burn) //If limb took enough damage, try to cut or tear it off if(owner && loc == owner && !is_stump()) if((limb_flags & ORGAN_CAN_AMPUTATE)) if((brute_dam + burn_dam) >= (max_damage * GLOB.config.organ_health_multiplier)) var/edge_eligible = FALSE var/blunt_eligible = FALSE var/dam_flags = 0 /// Certain limbs like zombie limbs have an integrated maiming bonus that make them easier to delimb. Add that. var/maim_bonus_to_add = src.maim_bonus if(isobj(used_weapon)) var/obj/W = used_weapon dam_flags = W.damage_flags() if(isprojectile(W)) var/obj/projectile/P = W if(dam_flags & DAMAGE_FLAG_BULLET) blunt_eligible = TRUE maim_bonus_to_add += P.maim_rate if(W.w_class >= w_class && (dam_flags & DAMAGE_FLAG_EDGE)) edge_eligible = TRUE if(!blunt_eligible && edge_eligible && (brute >= max_damage / (DROPLIMB_THRESHOLD_EDGE + maim_bonus_to_add))) droplimb(0, DROPLIMB_EDGE) else if(burn >= max_damage / ((dam_flags & DAMAGE_FLAG_LASER ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add)) droplimb(0, DROPLIMB_BURN) else if(blunt_eligible && brute >= max_damage / ((dam_flags & DAMAGE_FLAG_BULLET ? DROPLIMB_THRESHOLD_DESTROY_PROJECTILE : DROPLIMB_THRESHOLD_DESTROY) + maim_bonus_to_add)) droplimb(0, DROPLIMB_BLUNT) else if(brute >= max_damage / (DROPLIMB_THRESHOLD_TEAROFF + maim_bonus_to_add)) droplimb(0, DROPLIMB_EDGE) /obj/item/organ/external/heal_damage(brute, burn, internal = 0, robo_repair = 0) if(status & ORGAN_ROBOT && !robo_repair) return //Heal damage on the individual wounds for(var/datum/wound/W in wounds) if(brute == 0 && burn == 0) break // heal brute damage if(W.damage_type == INJURY_TYPE_BURN) burn = W.heal_damage(burn) else brute = W.heal_damage(brute) if(internal) status &= ~ORGAN_BROKEN perma_injury = 0 //Sync the organ's damage with its wounds update_damages() SEND_SIGNAL(src, COMSIG_UPDATE_LIMB_IMAGE) owner.updatehealth() return update_icon() /* This function completely restores a damaged organ to perfect condition. */ /obj/item/organ/external/rejuvenate() damage_state = "00" status &= ~ORGAN_DAMAGE_STATES perma_injury = 0 brute_dam = 0 burn_dam = 0 germ_level = 0 QDEL_LIST(wounds) number_wounds = 0 // handle internal organs for(var/obj/item/organ/current_organ in internal_organs) current_organ.rejuvenate() // remove embedded objects and drop them on the floor for(var/obj/implanted_object in implants) if(!istype(implanted_object,/obj/item/implant)) // We don't want to remove REAL implants. Just shrapnel etc. implanted_object.forceMove(owner.loc) implants -= implanted_object if(istype(tendon)) tendon.rejuvenate() owner.updatehealth() /obj/item/organ/external/proc/createwound(type = INJURY_TYPE_CUT, damage) if(damage <= 0 || !owner) return //moved this before the open_wound check so that having many small wounds for example doesn't somehow protect you from taking internal damage (because of the return) //Possibly trigger an internal wound, too. var/local_damage = brute_dam + burn_dam + damage var/is_burn_type_damage = (type in list(INJURY_TYPE_BURN, INJURY_TYPE_LASER)) var/is_brute_type_damage = (type in list(INJURY_TYPE_CUT, INJURY_TYPE_PIERCE, INJURY_TYPE_BRUISE)) if(damage > (min_broken_damage / 2) && local_damage > min_broken_damage && !(status & ORGAN_ROBOT) && is_brute_type_damage) if(prob(damage) && sever_artery()) owner.custom_pain("You feel something rip in your [name]!", 25) if(istype(tendon)) var/new_brute = damage if(min_broken_damage - damage > 0) // Only pass on damage that goes over the broken threshold new_brute = damage - min_broken_damage tendon.damage(new_brute) //Burn damage can cause fluid loss due to blistering and cook-off if(is_burn_type_damage && (damage > 5 || damage + burn_dam >= 15) && !BP_IS_ROBOTIC(src)) var/fluid_loss_severity switch(type) if(INJURY_TYPE_BURN) fluid_loss_severity = FLUIDLOSS_WIDE_BURN if(INJURY_TYPE_LASER) fluid_loss_severity = FLUIDLOSS_CONC_BURN var/fluid_loss = (damage/(owner.maxHealth - GLOB.config.health_threshold_dead)) * DEFAULT_BLOOD_AMOUNT * fluid_loss_severity owner.remove_blood_simple(fluid_loss) // first check whether we can widen an existing wound if(LAZYLEN(wounds) && prob(max(50+(number_wounds-1)*10,90))) if((type == INJURY_TYPE_CUT || type == INJURY_TYPE_BRUISE) && damage >= 5) //we need to make sure that the wound we are going to worsen is compatible with the type of damage... var/list/compatible_wounds = list() for (var/datum/wound/W in wounds) if (W.can_worsen(type, damage)) compatible_wounds += W if(length(compatible_wounds)) var/datum/wound/W = pick(compatible_wounds) W.open_wound(damage) if(bandage_level) owner.visible_message(SPAN_WARNING("The bandages on [owner.name]'s [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!"), SPAN_WARNING("The bandages on your [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!")) bandage_level = BANDAGE_LEVEL_NONE owner.update_bandages() if(prob(25)) if(status & ORGAN_ROBOT) owner.visible_message(SPAN_WARNING("The damage to [owner.name]'s [name] worsens."),\ SPAN_WARNING("The damage to your [name] worsens."),\ "You hear the screech of abused metal.") else owner.visible_message(SPAN_WARNING("The wound on [owner.name]'s [name] widens with a nasty ripping noise."),\ SPAN_WARNING("The wound on your [name] widens with a nasty ripping noise."),\ "You hear a nasty ripping noise, as if flesh is being torn apart.") return W //Creating wound var/wound_type = get_wound_type(type, damage) if(wound_type) var/datum/wound/W = new wound_type(damage, src) //Check whether we can add the wound to an existing wound for(var/datum/wound/other in wounds) if(other.can_merge(W)) other.merge_wound(W) W = null // to signify that the wound was added break LAZYADD(wounds, W) if(bandage_level) owner.visible_message(SPAN_WARNING("The bandages on [owner.name]'s [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!"), SPAN_WARNING("The bandages on your [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!")) bandage_level = BANDAGE_LEVEL_NONE owner.update_bandages() return W /**************************************************** PROCESSING & UPDATING ****************************************************/ //external organs handle brokenness a bit differently when it comes to damage. Instead brute_dam is checked inside process() //this also ensures that an external organ cannot be "broken" without broken_description being set. /obj/item/organ/external/is_broken() if(status & ORGAN_CUT_AWAY) return TRUE if((status & ORGAN_BROKEN) && !(status & ORGAN_SPLINTED)) for(var/obj/item/organ/internal/augment/aug in internal_organs) if(aug.supports_limb) if(aug.is_broken()) continue if(aug.is_bruised() && prob(60)) continue return FALSE return TRUE return FALSE //Determines if we even need to process this organ. /obj/item/organ/external/proc/need_process() if(BP_IS_ROBOTIC(src) && surge_damage) return TRUE if(is_tesla && owner) return TRUE if(BP_IS_ROBOTIC(src)) return FALSE if(get_pain()) return TRUE if(status & (ORGAN_CUT_AWAY|ORGAN_BLEEDING|ORGAN_BROKEN|ORGAN_DESTROYED|ORGAN_SPLINTED|ORGAN_DEAD|ORGAN_MUTATED)) return TRUE if(brute_dam || burn_dam) return TRUE if(last_dam != brute_dam + burn_dam) // Process when we are fully healed up. last_dam = brute_dam + burn_dam return TRUE else last_dam = brute_dam + burn_dam if(germ_level) return TRUE return FALSE /obj/item/organ/external/process() if(owner) //Specialized handling for tesla limbs. Checks if the limb currently has an associated tesla spine. Else, will disable the emissive and active overlays if(is_tesla) var/obj/item/organ/internal/augment/tesla/T = owner.internal_organs_by_name[BP_AUG_TESLA] if(T && !T.is_broken()) is_emissive = initial(is_emissive) is_overlay = initial(is_overlay) else is_emissive = FALSE is_overlay = FALSE return // Process wounds, doing healing etc. Only do this every few ticks to save processing power if(owner.life_tick % wound_update_accuracy == 0) update_wounds() //Chem traces slowly vanish if(owner.life_tick % 10 == 0) for(var/chemID in trace_chemicals) trace_chemicals[chemID] = trace_chemicals[chemID] - 1 if(trace_chemicals[chemID] <= 0) trace_chemicals.Remove(chemID) if(!(status & ORGAN_BROKEN)) perma_injury = 0 if(status & ORGAN_NYMPH) var/datum/component/nymph_limb/N = GetComponent(/datum/component/nymph_limb) N.handle_nymph(src) if(surge_damage && (status & ORGAN_ASSISTED)) tick_surge_damage() //Yes, this being here is intentional since this proc does not call ..() unless the owner is null. //Infections update_germs() //check if an online RIG can splint the broken bone check_rigsplints() else ..() /obj/item/organ/external/do_surge_effects() if(prob(surge_damage)) owner.custom_pain("The artificial nerves in your [name] scream out in pain!", surge_damage/6) /obj/item/organ/external/proc/check_rigsplints() if((status & ORGAN_BROKEN) && !(status & ORGAN_SPLINTED)) if(istype(owner,/mob/living/carbon/human)) var/mob/living/carbon/human/H = owner if(H.back && istype(H.back, /obj/item/rig)) var/obj/item/rig/R = H.back if(R.offline) return if(H.wear_suit && istype(H.wear_suit,/obj/item/clothing/suit/space)) var/obj/item/clothing/suit/space/suit = H.wear_suit if(isnull(suit.supporting_limbs)) return to_chat(owner, SPAN_WARNING("You feel \the [suit] constrict about your [name], supporting it.")) status |= ORGAN_SPLINTED suit.supporting_limbs |= src owner.update_hud_hands() //Updating germ levels. Handles organ germ levels and necrosis. /* The INFECTION_LEVEL values defined in setup.dm control the time it takes to reach the different infection levels. Since infection growth is exponential, you can adjust the time it takes to get from one germ_level to another using the rough formula: desired_germ_level = initial_germ_level*e^(desired_time_in_seconds/1000) So if I wanted it to take an average of 15 minutes to get from level one (100) to level two I would set INFECTION_LEVEL_TWO to 100*e^(15*60/1000) = 245. Note that this is the average time, the actual time is dependent on RNG. INFECTION_LEVEL_ONE below this germ level nothing happens, and the infection doesn't grow INFECTION_LEVEL_TWO above this germ level the infection will start to spread to internal and adjacent organs INFECTION_LEVEL_THREE above this germ level the player will take additional toxin damage per second, and will die in minutes without antitox. also, above this germ level you will need to overdose on thetamycin to reduce the germ_level. Note that amputating the affected organ does in fact remove the infection from the player's body. */ /obj/item/organ/external/proc/update_germs() if(status & (ORGAN_ROBOT) || (owner.species && owner.species.flags & NO_BLOOD)) //Robotic limbs shouldn't be infected, nor should nonexistant limbs, or bloodless species. germ_level = 0 return if(germ_level <= 0) //Catch any weirdness that might happen with negative values germ_level = 0 if(owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs //** Syncing germ levels with external wounds handle_germ_sync() //** Handle antibiotics and curing infections handle_antibiotics() //** Handle the effects of infections handle_germ_effects() /obj/item/organ/external/proc/handle_germ_sync() var/antibiotics = 0 if(CE_ANTIBIOTIC in owner.chem_effects) antibiotics = owner.chem_effects[CE_ANTIBIOTIC] for(var/datum/wound/W in wounds) //Open wounds can become infected if (owner.germ_level > W.germ_level && W.infection_check()) W.germ_level++ if(antibiotics < 5) for(var/datum/wound/W in wounds) //Infected wounds raise the organ's germ level if (W.germ_level > germ_level && W.infection_check()) germ_level++ break //limit increase to a maximum of one per second /obj/item/organ/external/proc/get_infect_target(var/list/infect_candidates = list()) var/obj/item/organ/temp_target shuffle(infect_candidates) //Slightly randomizes since if all germ levels are zero, it'll always be the first pick of the list //figure out which organs we can spread germs to for (var/obj/item/organ/I in infect_candidates) if(I.germ_level < min(germ_level, INFECTION_LEVEL_TWO)) //Only choose organs that have less germs than us AND are below level two //The below will always be the organ with the highest germ level. It picks a temp_target first then cycles through to find which, if any, has more germs. if(!temp_target || I.germ_level > temp_target.germ_level) temp_target = I //This will always be the organ with the highest germ level return temp_target /obj/item/organ/external/handle_germ_effects() var/antibiotics = 0 if(CE_ANTIBIOTIC in owner.chem_effects) antibiotics = owner.chem_effects[CE_ANTIBIOTIC] if(germ_level < INFECTION_LEVEL_TWO) //null out the infect targets since at this point we're not in danger of spreading our infection. infect_target_internal = null infect_target_external = null return ..() germ_level++ if(germ_level >= INFECTION_LEVEL_TWO && REAGENT_VOLUME(owner.reagents, /singleton/reagent/thetamycin) < 5) //The presence of 5 units of thetamycin will stop infections spreading //SPREADING TO INTERNAL ORGANS if(isnull(infect_target_internal) || QDELETED(infect_target_internal)) infect_target_internal = get_infect_target(internal_organs) else if(prob(25) || !infect_target_internal.is_infected()) //Increase steadily until infection_level_one, then only bump the germ level now and then infect_target_internal.germ_level++ //slowly increase the infection level //Check to see if the other organ is as infected or more infected than us. If so, we need to get a new target on the next process if(infect_target_internal.germ_level >= min(germ_level, INFECTION_LEVEL_TWO)) infect_target_internal = null //SPREADING TO CHILD AND PARENT EXTERNAL ORGANS if(isnull(infect_target_external) || QDELETED(infect_target_internal)) var/list/temp_targets = list() if(children) for (var/obj/item/organ/external/child in children) if (child.germ_level < germ_level && !(child.status & ORGAN_ROBOT)) temp_targets += child if(parent) if(parent.germ_level < germ_level && !(parent.status & ORGAN_ROBOT)) temp_targets += parent if(length(temp_targets)) infect_target_external = get_infect_target(temp_targets) else if(prob(25) || !infect_target_external.is_infected()) //Increase steadily until sufficiently infected, then only bump the germ level now and then infect_target_external.germ_level++ //slowly increase the infection level //Check to see if the other organ is as infected or more infected than us. If so, we need to get a new target on the next process if(infect_target_external.germ_level >= min(germ_level, INFECTION_LEVEL_TWO)) infect_target_external = null if(germ_level >= INFECTION_LEVEL_THREE && antibiotics < 20) //overdosing is necessary to stop severe infections if (!(status & ORGAN_DEAD)) status |= ORGAN_DEAD to_chat(owner, SPAN_NOTICE("You can't feel your [name] anymore...")) owner.update_body(1) germ_level++ owner.adjustToxLoss(1) /obj/item/organ/external/proc/body_part_class() return null /obj/item/organ/external/proc/covered_bleed_report(var/blood_type) return "[owner.get_pronoun("has")] [blood_type] soaking through the clothes on [owner.get_pronoun("his")] [src]!" //Updating wounds. Handles wound natural I had some free spachealing, internal bleedings and infections /obj/item/organ/external/proc/update_wounds() if((status & ORGAN_ROBOT) || (status & ORGAN_ADV_ROBOT) || (status & ORGAN_PLANT)) //Robotic limbs don't heal or get worse. Diona limbs heal using their own mechanic return var/updatehud for(var/datum/wound/W in wounds) // wounds can disappear after 10 minutes at the earliest if(W.damage <= 0 && W.created + (10 MINUTES) <= world.time) qdel(W) continue // let the GC handle the deletion of the wound if (W.damage > 0) updatehud = TRUE //If there are any wounds with damage to heal, then we'll update health huds // slow healing var/heal_amt = 0 // if damage >= 50 AFTER treatment then it's probably too severe to heal within the timeframe of a round. if (W.can_autoheal() && W.wound_damage() && brute_ratio < 50 && burn_ratio < 50) heal_amt += 0.5 //we only update wounds once in [wound_update_accuracy] ticks so have to emulate realtime heal_amt = heal_amt * wound_update_accuracy //configurable regen speed woo, no-regen hardcore or instaheal hugbox, choose your destiny heal_amt = heal_amt * GLOB.config.organ_regeneration_multiplier // amount of healing is spread over all the wounds heal_amt = heal_amt / (LAZYLEN(wounds) + 1) // making it look prettier on scanners heal_amt = round(heal_amt,0.1) var/dam_type = DAMAGE_BRUTE if (W.damage_type == INJURY_TYPE_BURN) dam_type = DAMAGE_BURN if(owner.can_autoheal(dam_type) && (heal_amt > 0)) W.heal_damage(heal_amt) // Salving also helps against infection if(W.germ_level > 0 && W.salved && prob(2)) W.disinfected = TRUE W.germ_level = 0 // sync the organ's damage with its wounds src.update_damages() if (updatehud) owner.hud_updateflag = 1022 if(update_icon()) SEND_SIGNAL(src, COMSIG_UPDATE_LIMB_IMAGE) owner.UpdateDamageIcon(1) //Updates brute_damn and burn_damn from wound damages. Updates BLEEDING status. /obj/item/organ/external/proc/update_damages() number_wounds = 0 brute_dam = 0 burn_dam = 0 var/cut_dam = 0 status &= ~ORGAN_BLEEDING var/clamped = 0 var/mob/living/carbon/human/H if(istype(owner,/mob/living/carbon/human)) H = owner //update damage counts for(var/datum/wound/W in wounds) if(W.damage <= 0) qdel(W) continue if(W.damage_type == INJURY_TYPE_BURN) burn_dam += W.damage else brute_dam += W.damage if(W.damage_type == INJURY_TYPE_CUT) cut_dam += W.damage if(!(status & ORGAN_ROBOT) && W.bleeding() && (H && !(H.species.flags & NO_BLOOD))) W.handle_bleeding(H, src) clamped |= W.clamped number_wounds += W.amount //things tend to bleed if they are CUT OPEN if (open && !clamped && (H && !(H.species.flags & NO_BLOOD) && !(status & ORGAN_ROBOT))) status |= ORGAN_BLEEDING if (istype(tendon)) tendon.update_damage(cut_dam - min_broken_damage) update_damage_ratios() /obj/item/organ/external/proc/update_damage_ratios() var/limb_loss_threshold = max_damage * 2 brute_ratio = Percent(brute_dam, limb_loss_threshold) burn_ratio = Percent(burn_dam, limb_loss_threshold) // new damage icon system // returns just the brute/burn damage code /obj/item/organ/external/proc/damage_state_text() var/tburn = 0 var/tbrute = 0 if(burn_dam ==0) tburn =0 else if (burn_dam < (max_damage * 0.25 / 2)) tburn = 1 else if (burn_dam < (max_damage * 0.75 / 2)) tburn = 2 else tburn = 3 if (brute_dam == 0) tbrute = 0 else if (brute_dam < (max_damage * 0.25 / 2)) tbrute = 1 else if (brute_dam < (max_damage * 0.75 / 2)) tbrute = 2 else tbrute = 3 return "[tbrute][tburn]" /**************************************************** DISMEMBERMENT ****************************************************/ /obj/item/organ/external/proc/post_droplimb(mob/living/carbon/human/victim) victim.updatehealth() victim.UpdateDamageIcon() victim.regenerate_icons() dir = 2 //Handles dismemberment /obj/item/organ/external/proc/droplimb(clean, disintegrate = DROPLIMB_EDGE, ignore_children = null) if(!(limb_flags & ORGAN_CAN_AMPUTATE) || !owner) return switch(disintegrate) if(DROPLIMB_EDGE) if(!clean) var/gore_sound = "[(status & ORGAN_ROBOT) ? "tortured metal" : "ripping tendons and flesh"]" owner.visible_message( SPAN_DANGER("\The [owner]'s [src.name] flies off in an arc!"),\ "Your [src.name] goes flying off!",\ SPAN_DANGER("You hear the terrible sound of [gore_sound].")) if(DROPLIMB_BURN) var/gore = "[(status & ORGAN_ROBOT) ? "": " of burning flesh"]" owner.visible_message( SPAN_DANGER("\The [owner]'s [src.name] flashes away into ashes!"),\ "Your [src.name] flashes away into ashes!",\ SPAN_DANGER("You hear the crackling sound[gore].")) if(DROPLIMB_BLUNT) var/gore = "[(status & ORGAN_ROBOT) ? "": " in a shower of gore"]" var/gore_sound = "[(status & ORGAN_ROBOT) ? "rending sound of tortured metal" : "sickening splatter of gore"]" owner.visible_message( SPAN_DANGER("\The [owner]'s [src.name] explodes[gore]!"),\ "Your [src.name] explodes[gore]!",\ SPAN_DANGER("You hear the [gore_sound].")) var/mob/living/carbon/human/victim = owner //Keep a reference for post-removed(). var/obj/item/organ/external/original_parent = parent if(!clean) victim.shock_stage += min_broken_damage victim.flash_strong_pain() var/mob/living/carbon/human/last_owner = owner removed(null, ignore_children) if(istype(last_owner) && !QDELETED(last_owner) && length(last_owner.organs) <= 1) last_owner.drop_all_limbs(disintegrate) // drops the last remaining part, usually the torso, as an item if(original_parent) var/datum/wound/lost_limb/W = new(src, disintegrate, clean) var/obj/item/organ/external/damaged_organ = original_parent if(!clean) var/obj/item/organ/external/stump/stump = new (victim, 0, src) stump.add_pain(max_damage) damaged_organ = stump if(disintegrate != DROPLIMB_BURN) stump.sever_artery() W.parent_organ = damaged_organ LAZYADD(damaged_organ.wounds, W) damaged_organ.update_damages() post_droplimb(victim) SEND_SIGNAL(victim, COMSIG_LIMB_LOSS) switch(disintegrate) if(DROPLIMB_EDGE) // compile_icon() used to be here, but it's causing issues, so RIP. add_blood(victim) var/matrix/M = matrix() M.Turn(rand(180)) src.transform = M forceMove(get_turf(src)) if(!clean) //Throw limb around. if(src && isturf(loc)) INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(src,pick(GLOB.alldirs)), rand(1,3), 4) dir = 2 if(DROPLIMB_BURN) new /obj/effect/decal/cleanable/ash(get_turf(victim)) for(var/obj/item/I in src) if(I.w_class > 2 && !istype(I,/obj/item/organ)) I.forceMove(get_turf(src)) qdel(src) if(DROPLIMB_BLUNT) var/obj/effect/decal/cleanable/blood/gibs/gore = new victim.species.single_gib_type(get_turf(victim)) if(victim.species.flesh_color) gore.fleshcolor = victim.species.flesh_color if(victim.get_blood_color()) gore.basecolor = victim.get_blood_color() gore.update_icon() // Tiny amount of blood and not impacted by anything like coagulants, etc- we just want it for effect. victim.blood_squirt(2, loc, rand(2,5)) add_blood(victim) INVOKE_ASYNC(gore, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 4) var/turf/Tloc = get_turf(src) for(var/obj/item/I in src) I.dropInto(Tloc) INVOKE_ASYNC(I, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 4) qdel(src) /**************************************************** HELPERS ****************************************************/ /obj/item/organ/external/proc/is_stump() return FALSE /obj/item/organ/external/proc/release_restraints(var/mob/living/carbon/human/holder) if(!holder) holder = owner if(!holder) return if (holder.handcuffed && (body_part in list(ARM_LEFT, ARM_RIGHT, HAND_LEFT, HAND_RIGHT))) holder.visible_message(\ "\The [holder.handcuffed.name] falls off of [holder.name].",\ "\The [holder.handcuffed.name] falls off you.") holder.drop_from_inventory(holder.handcuffed) if (holder.legcuffed && (body_part in list(FOOT_LEFT, FOOT_RIGHT, LEG_LEFT, LEG_RIGHT))) holder.visible_message(\ "\The [holder.legcuffed.name] falls off of [holder.name].",\ "\The [holder.legcuffed.name] falls off you.") holder.drop_from_inventory(holder.legcuffed) // checks if all wounds on the organ are bandaged /obj/item/organ/external/proc/is_bandaged() for(var/datum/wound/W in wounds) if(!W.bandaged) return 0 return 1 // checks if all wounds on the organ are salved /obj/item/organ/external/proc/is_salved() for(var/datum/wound/W in wounds) if(!W.salved) return 0 return 1 // checks if all wounds on the organ are disinfected /obj/item/organ/external/proc/is_disinfected() for(var/datum/wound/W in wounds) if(!W.disinfected) return 0 return 1 /obj/item/organ/external/proc/bandage() var/rval = 0 status &= ~ORGAN_BLEEDING for(var/datum/wound/W in wounds) rval |= !W.bandaged W.bandage() return rval /obj/item/organ/external/proc/salve() var/rval = 0 for(var/datum/wound/W in wounds) rval |= !W.salved W.salve() return rval /obj/item/organ/external/proc/disinfect() var/rval = 0 for(var/datum/wound/W in wounds) rval |= !W.disinfected W.disinfect() W.germ_level = 0 return rval /obj/item/organ/external/proc/clamp_organ() var/rval = 0 src.status &= ~ORGAN_BLEEDING for(var/datum/wound/W in wounds) rval |= !W.clamped W.clamped = 1 return rval /// Fractures the bone, so long as it isn't robotic or already broken. When the silent flag is set, no message or sound will be played, and there will be no pain effects /obj/item/organ/external/proc/fracture(var/silent = FALSE) if(status & ORGAN_ROBOT) return //ORGAN_BROKEN doesn't have the same meaning for robot limbs if((status & ORGAN_BROKEN) || !(limb_flags & ORGAN_CAN_BREAK)) return if(QDELETED(owner)) return if(!silent) var/message = pick("broke in half", "shattered") owner.visible_message(\ SPAN_WARNING("You hear a loud cracking sound coming from \the [owner]!"),\ SPAN_DANGER("Something feels like it [message] in your [name]!"),\ "You hear a sickening crack!") if(owner.species && owner.can_feel_pain()) owner.emote("scream") owner.flash_strong_pain() playsound(src.loc, SFX_FRACTURE, 100, 1, -2) status |= ORGAN_BROKEN broken_description = pick("broken", "fracture", "hairline fracture") perma_injury = brute_dam // Fractures have a chance of getting you out of restraints if(prob(25)) release_restraints() // This is mostly for the ninja suit to stop ninja being so crippled by breaks. check_rigsplints() /obj/item/organ/external/proc/mend_fracture() if(status & ORGAN_ROBOT) return 0 //ORGAN_BROKEN doesn't have the same meaning for robot limbs if(brute_dam > min_broken_damage * GLOB.config.organ_health_multiplier) return 0 //will just immediately fracture again status &= ~ORGAN_BROKEN return 1 /obj/item/organ/external/robotize(var/company) ..() if(company) model = company var/datum/robolimb/R = GLOB.all_robolimbs[company] if(R) if(!force_skintone || override_robotize_force_icon) force_icon = override_robotize_force_icon ? override_robotize_force_icon : R.icon if(R.lifelike) status |= ORGAN_LIFELIKE if(force_prosthetic_name) name = force_prosthetic_name else name = "[initial(name)]" else if(force_prosthetic_name) name = force_prosthetic_name else name = "[R.company] [initial(name)]" desc = "[R.desc]" if(R.paintable || !isnull(override_robotize_painted)) painted = !isnull(override_robotize_painted) ? override_robotize_painted : TRUE if(R.emissive) is_emissive = TRUE else is_emissive = FALSE if(R.overlay) is_overlay = TRUE else is_overlay = FALSE if(R.is_tesla) is_tesla = TRUE else is_tesla = FALSE brute_mod = R.brute_mod burn_mod = R.burn_mod robotize_type = company augment_limit += 1 //robotic limbs get one extra augment capacity dislocated = -1 //TODO, make robotic limbs a separate type, remove snowflake limb_flags &= ~ORGAN_CAN_BREAK get_icon() unmutate() if(robotize_children) for (var/obj/item/organ/external/T in children) T.robotize(company) /obj/item/organ/external/mechassist() ..() limb_flags |= ORGAN_CAN_BREAK /obj/item/organ/external/proc/robotize_advanced() status |= ORGAN_ADV_ROBOT for (var/obj/item/organ/external/T in children) if (T) T.robotize_advanced() /obj/item/organ/external/proc/mutate() if(BP_IS_ROBOTIC(src)) return src.status |= ORGAN_MUTATED if(owner) owner.update_body() /obj/item/organ/external/proc/unmutate() if(!BP_IS_ROBOTIC(src)) src.status &= ~ORGAN_MUTATED if(owner) owner.update_body() /obj/item/organ/external/proc/get_damage() //returns total damage return max(brute_dam + burn_dam - perma_injury, perma_injury) //could use max_damage? /obj/item/organ/external/proc/has_infected_wound() for(var/datum/wound/W in wounds) if(W.germ_level > INFECTION_LEVEL_ONE) return 1 return 0 /obj/item/organ/external/is_usable() if(is_stump()) return FALSE if(ORGAN_IS_DISLOCATED(src)) return FALSE if(tendon_status() & TENDON_CUT) return FALSE if(parent && (parent.tendon_status() & TENDON_CUT)) return FALSE if(get_pain() > pain_disability_threshold) return FALSE if(brute_ratio >= 100) return FALSE if(burn_ratio >= 100) return FALSE return ..() /obj/item/organ/external/proc/is_malfunctioning() if(BP_IS_ROBOTIC(src) && (brute_ratio + burn_ratio) >= 10 && prob(brute_dam + burn_dam) || (surge_damage > (MAXIMUM_SURGE_DAMAGE * 0.25))) return TRUE if(robotize_type) var/datum/robolimb/R = GLOB.all_robolimbs[robotize_type] if(R.malfunctioning_check(owner)) return TRUE else return FALSE /obj/item/organ/external/proc/embed(obj/item/W, silent = FALSE, supplied_message, datum/wound/supplied_wound) if(!owner || loc != owner) return if(!W.canremove || is_robot_module(W)) //Modules and augments cannot embed return if(species.flags & NO_EMBED) return if(!silent) if(supplied_message) owner.visible_message(SPAN_DANGER("[supplied_message]")) else owner.visible_message(SPAN_DANGER("\The [W] sticks in [owner]'s wound!"), SPAN_DANGER("\The [W] sticks in your wound!")) if(supplied_wound) for(var/datum/wound/wound in wounds) if ((wound.damage_type == INJURY_TYPE_CUT || wound.damage_type == INJURY_TYPE_PIERCE) && wound.damage >= W.w_class * 5) supplied_wound = wound break if(!supplied_wound || (W in supplied_wound.embedded_objects)) // Just in case. return LAZYADD(supplied_wound.embedded_objects, W) implants += W owner.embedded_flag = 1 add_verb(owner, /mob/proc/yank_out_object) W.add_blood(owner) if(ismob(W.loc)) var/mob/living/H = W.loc H.drop_from_inventory(W,owner) W.forceMove(owner) /obj/item/organ/external/removed(var/mob/living/user, var/ignore_children = 0) if(!owner) return var/is_robotic = BP_IS_ROBOTIC(src) var/mob/living/carbon/human/victim = owner ..(null, user) victim.bad_external_organs -= src for(var/atom/movable/implant in implants) //large items and non-item objs fall to the floor, everything else stays var/obj/item/I = implant if(istype(I) && I.w_class < WEIGHT_CLASS_NORMAL) implant.forceMove(src) // let actual implants still inside know they're no longer implanted if(istype(I, /obj/item/implant)) var/obj/item/implant/imp_device = I imp_device.removed() else implants.Remove(implant) implant.forceMove(get_turf(src)) // Attached organs also fly off. if(!ignore_children) for(var/obj/item/organ/external/O in children) O.removed() if(!QDELETED(O)) O.forceMove(src) // if we didn't lose the organ we still want it as a child children += O O.parent = src // Grab all the internal giblets too. for(var/obj/item/organ/organ in internal_organs) organ.removed(user, FALSE, FALSE) // Organ stays inside and connected organ.forceMove(src) // Remove parent references parent?.children -= src parent = null release_restraints(victim) victim.organs -= src victim.organs_by_name[limb_name] = null // Remove from owner's vars. victim.organs_by_name -= organ_tag //Robotic limbs explode if sabotaged. if(is_robotic && sabotaged) victim.visible_message( SPAN_DANGER("\The [victim]'s [src.name] explodes violently!"),\ SPAN_DANGER("Your [src.name] explodes!"),\ SPAN_DANGER("You hear an explosion!")) explosion(get_turf(owner),-1,-1,2,3) spark(victim, 5) qdel(src) /obj/item/organ/external/proc/disfigure(var/type = "brute") if (disfigured) return if(owner) if(type == "brute") owner.visible_message(SPAN_WARNING("You hear a sickening cracking sound coming from \the [owner]'s [name]."), \ SPAN_DANGER("Your [name] becomes a mangled mess!"), \ SPAN_WARNING("You hear a sickening crack.")) else owner.visible_message(SPAN_WARNING("\The [owner]'s [name] melts away, turning into a mangled mess!"), \ SPAN_DANGER("Your [name] melts away!"), \ SPAN_WARNING("You hear a sickening sizzle.")) disfigured = 1 /obj/item/organ/external/proc/get_wounds_desc() . = "" if(status & ORGAN_DESTROYED && !is_stump()) . += "tear at [amputation_point] so severe that it hangs by a scrap of flesh" //Handle robotic and synthetic organ damage if(status & ORGAN_ASSISTED) var/LL = status & ORGAN_LIFELIKE if(brute_dam) switch(brute_dam) if(0 to 20) . += "some [LL ? pick ("cuts","bruises","scars") : "dents"]" if(21 to INFINITY) . += "[LL ? pick("exposed wiring","torn-back synthflesh") : pick("a lot of dents","severe denting")]" if(brute_dam && burn_dam) . += " and " if(burn_dam) switch(burn_dam) if(0 to 20) . += "some [LL ? pick ("fresh skins","burn scars","healing burns") : "burns"]" if(21 to INFINITY) . += "[LL ? pick("roasted synth-flesh","melted internal wiring") : pick("many burns","scorched metal")]" if(open) if(brute_dam || burn_dam) . += " and " if(open == 1) . += "some exposed screws" else . += "an open panel" return var/list/wound_descriptors = list() if(open > 1) wound_descriptors["an open incision"] = 1 else if (open) wound_descriptors["an incision"] = 1 for(var/datum/wound/W in wounds) var/this_wound_desc = W.desc if(W.damage_type == DAMAGE_BURN && W.salved) this_wound_desc = "salved [this_wound_desc]" if(W.bleeding()) if(W.wound_damage() > W.bleed_threshold) this_wound_desc = "bleeding [this_wound_desc]" else this_wound_desc = "bleeding [this_wound_desc]" else if(W.bandaged == TRUE) this_wound_desc = "bandaged [this_wound_desc]" if(W.germ_level > 600) this_wound_desc = "badly infected [this_wound_desc]" else if(W.germ_level > 330) this_wound_desc = "lightly infected [this_wound_desc]" if(wound_descriptors[this_wound_desc]) wound_descriptors[this_wound_desc] += W.amount else wound_descriptors[this_wound_desc] = W.amount if(open > 1) var/bone = encased ? encased : "bone" if(status & ORGAN_BROKEN) bone = "broken [bone]" wound_descriptors["a [bone] exposed"] = 1 if(!encased || open > 1) var/list/bits = list() for(var/obj/item/organ/internal/organ in internal_organs) bits += organ.get_visible_state() if(length(bits)) wound_descriptors["[english_list(bits)] visible in the wounds"] = 1 if(wound_descriptors.len) var/list/flavor_text = list() var/list/no_exclude = list("gaping wound", "big gaping wound", "massive wound", "large bruise",\ "huge bruise", "massive bruise", "severe burn", "large burn", "deep burn", "carbonised area") //note to self make this more robust for(var/wound in wound_descriptors) switch(wound_descriptors[wound]) if(1) flavor_text += "[prob(10) && !(wound in no_exclude) ? "what might be " : ""]a [wound]" if(2) flavor_text += "[prob(10) && !(wound in no_exclude) ? "what might be " : ""]a pair of [wound]s" if(3 to 5) flavor_text += "several [wound]s" if(6 to INFINITY) flavor_text += "a ton of [wound]\s" return english_list(flavor_text) /obj/item/organ/external/listen() var/list/sounds = list() for(var/obj/item/organ/internal/I in internal_organs) var/gutsound = I.listen() if(gutsound) sounds += gutsound if(!sounds.len) if(owner.pulse()) sounds += "faint pulse" return sounds /obj/item/organ/external/proc/sever_artery() if((status & ORGAN_ROBOT) || (status & ORGAN_ARTERY_CUT) || !species || species.flags & NO_BLOOD || species.flags & NO_ARTERIES) return FALSE else status |= ORGAN_ARTERY_CUT return TRUE /obj/item/organ/external/proc/tendon_status() if(!(limb_flags & ORGAN_HAS_TENDON)) return if(!istype(tendon)) // Somehow this limb should have a tendon but doesn't. Assume it was destroyed return TENDON_CUT return tendon.status // Damage procs /obj/item/organ/external/proc/get_brute_damage() return brute_dam /obj/item/organ/external/proc/get_burn_damage() return burn_dam /obj/item/organ/external/proc/get_genetic_damage() return BP_IS_ROBOTIC(src) ? 0 : genetic_degradation /obj/item/organ/external/proc/remove_genetic_damage(var/amount) if(BP_IS_ROBOTIC(src) || (species.flags & NO_SCAN)) genetic_degradation = 0 status &= ~ORGAN_MUTATED return var/last_gene_dam = genetic_degradation genetic_degradation = min(100,max(0,genetic_degradation - amount)) if(genetic_degradation <= 30) if(status & ORGAN_MUTATED) unmutate() to_chat(src, SPAN_NOTICE("Your [name] is shaped normally again.")) return -(genetic_degradation - last_gene_dam) /obj/item/organ/external/proc/add_genetic_damage(var/amount) if(BP_IS_ROBOTIC(src) || (species.flags & NO_SCAN)) genetic_degradation = 0 status &= ~ORGAN_MUTATED return var/last_gene_dam = genetic_degradation genetic_degradation = min(100,max(0,genetic_degradation + amount)) if(genetic_degradation > 10) owner.infest_with_parasite(owner, BP_TUMOUR_SPREADING, src) if(genetic_degradation > 20) if(!(status & ORGAN_MUTATED) && prob(genetic_degradation)) mutate() to_chat(owner, SPAN_NOTICE("Something is not right with your [name]...")) return (genetic_degradation - last_gene_dam) // Pain/halloss /obj/item/organ/external/proc/get_pain() if(!ORGAN_CAN_FEEL_PAIN(src) || BP_IS_ROBOTIC(src)) return 0 . = pain + 0.7 * brute_dam + 0.8 * burn_dam + 0.5 * get_genetic_damage() if(is_broken()) . += 10 else if(ORGAN_IS_DISLOCATED(src)) . += 5 for(var/obj/item/organ/internal/I as anything in internal_organs) . += 0.3 * I.getToxLoss() /obj/item/organ/external/proc/remove_pain(var/amount) if(!ORGAN_CAN_FEEL_PAIN(src)) pain = 0 return var/last_pain = pain pain = max(0, min(species.total_health * 2, pain - amount)) return -(pain-last_pain) /obj/item/organ/external/proc/add_pain(var/amount) if(!ORGAN_CAN_FEEL_PAIN(src)) pain = 0 return var/last_pain = pain if(owner) amount *= owner.species.pain_mod if(HAS_TRAIT(owner, TRAIT_ORIGIN_PAIN_RESISTANCE)) amount = max(amount-1, 1) amount -= (owner.chem_effects[CE_PAINKILLER]/3) if(amount <= 0) return pain = max(0, min(pain + amount, species.total_health * 2)) if(owner && ((amount > 15 && prob(20)) || (amount > 30 && prob(60)))) owner.emote("scream") if(amount > 5 && owner) owner.undo_srom_pull() SEND_SIGNAL(src, COMSIG_UPDATE_LIMB_IMAGE) return pain-last_pain /mob/living/carbon/human/proc/undo_srom_pull() if(srom_pulled_by) to_chat(src, SPAN_DANGER("You are ripped out of the Srom by a sudden shock!")) var/mob/living/carbon/human/srom_puller = srom_pulling.resolve() srom_puller.srom_pulling = null to_chat(srom_puller, SPAN_WARNING("A vibration like a jackhammer resonates in your consciousness, and the person you pulled into the Srom disappears in the next instant.")) srom_pulled_by = null if(srom_pulling) to_chat(src, SPAN_DANGER("Your Srom pull is disturbed by a sudden shock!")) var/mob/living/carbon/human/victim = srom_pulling.resolve() victim.srom_pulled_by = null srom_pulling = null