Files
Aurora.3/code/modules/organs/organ_external.dm
Greenjoe12345 6021ed948f Roundstart broken bones (#20936)
Partial revival of https://github.com/Aurorastation/Aurora.3/pull/19923
(just the broken bones part)

---------

Co-authored-by: Geeves <ggrobler447@gmail.com>
2025-07-10 21:40:00 +00:00

1657 lines
53 KiB
Plaintext

/****************************************************
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.
var/brute_mod = 1
///Actual current brute damage
var/brute_dam = 0
///Ratio of current brute damage to max damage
var/brute_ratio = 0
var/burn_mod = 1
///Actual current burn damage
var/burn_dam = 0
///Ratio of current burn damage to max damage
var/burn_ratio = 0
var/last_dam = -1
///Amount of current genetic damage
var/genetic_degradation = 0
///How much the limb hurts
var/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/datum/wound/wounds = list()
///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/proc/invalidate_marking_cache()
cached_markings = null
/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
QDEL_LIST(children)
QDEL_LIST(internal_organs)
QDEL_LIST(wounds)
QDEL_LIST(implants)
infect_target_internal = null
infect_target_external = null
applied_pressure = null
QDEL_NULL(nymph)
QDEL_NULL(tendon)
. = ..()
/obj/item/organ/external/attack_self(var/mob/user)
if(!contents.len)
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(removable_objects.len)
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("<b>[user]</b> cuts [src] open with [attacking_item]!"))
stage++
return
if(1)
if(istype(attacking_item, /obj/item/surgery/retractor))
user.visible_message(SPAN_DANGER("<b>[user]</b> cracks [src] open like an egg with [attacking_item]!"))
stage++
return
if(2)
if(istype(attacking_item, /obj/item/surgery/hemostat))
if(contents.len)
var/obj/item/removing = pick(contents)
removing.forceMove(get_turf(user.loc))
if(!(user.l_hand && user.r_hand))
user.put_in_hands(removing)
user.visible_message(SPAN_DANGER("<b>[user]</b> extracts [removing] from [src] with [attacking_item]!"))
else
user.visible_message(SPAN_DANGER("<b>[user]</b> fishes around fruitlessly in [src] with [attacking_item]."))
return
..()
/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/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()
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/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 src)
organ.replaced(owner,src)
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)
parent.wounds -= W
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)
if((brute <= 0) && (burn <= 0))
return 0
brute *= brute_mod
burn *= burn_mod
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)
if(!owner.has_psionics())
brute *= 0.9
burn *= 0.9
else if(owner.psi)
var/psi_rank = owner.psi.get_rank()
if(psi_rank == PSI_RANK_SENSITIVE)
brute *= 1.05
burn *= 1.05
else if(psi_rank == PSI_RANK_HARMONIOUS)
brute *= 1.1
burn *= 1.1
else if(psi_rank == PSI_RANK_APEX)
brute *= 1.2
burn *= 1.2
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/can_cut = !BP_IS_ROBOTIC(src) && (sharp || prob(brute))
if(brute)
var/to_create = BRUISE
if(can_cut)
to_create = 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 = PIERCE
createwound(to_create, brute)
if(burn)
if(laser)
createwound(LASER, burn)
else
createwound(DAMAGE_BURN, burn)
add_pain(0.6 * burn + 0.4 * 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
return update_icon()
/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(encased && !(status & ORGAN_BROKEN)) //ribs protect
organ_hit_chance *= 0.2
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 == DAMAGE_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()
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
wounds.Cut()
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(var/type = CUT, var/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(DAMAGE_BURN, LASER))
if(damage > (min_broken_damage / 2) && local_damage > min_broken_damage && !(status & ORGAN_ROBOT))
if(!is_burn_type_damage)
if(prob(damage) && sever_artery())
owner.custom_pain("You feel something rip in your [name]!", 25)
if(istype(tendon) && type == CUT)
var/new_brute = damage
if(min_broken_damage - brute_dam > 0)
// Only pass on damage that goes over the broken threshold
new_brute = brute_dam + 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(DAMAGE_BURN)
fluid_loss_severity = FLUIDLOSS_WIDE_BURN
if(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(wounds.len > 0 && prob(max(50+(number_wounds-1)*10,90)))
if((type == CUT || 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(compatible_wounds.len)
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
//Creating wound
var/wound_type = get_wound_type(type, damage)
if(wound_type)
var/datum/wound/W = new wound_type(damage)
//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
if(W)
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()
/****************************************************
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((status & ORGAN_ASSISTED) && 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 + 6000 <= world.time)
wounds -= W
continue
// let the GC handle the deletion of the wound
if (W.damage > 0)
updatehud = 1//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() < 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 / (wounds.len + 1)
// making it look prettier on scanners
heal_amt = round(heal_amt,0.1)
W.heal_damage(heal_amt)
// Salving also helps against infection
if(W.germ_level > 0 && W.salved && prob(2))
W.disinfected = 1
W.germ_level = 0
// sync the organ's damage with its wounds
src.update_damages()
if (updatehud)
owner.hud_updateflag = 1022
if(update_icon())
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_type == DAMAGE_BURN)
burn_dam += W.damage
else
brute_dam += W.damage
if(W.damage_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
brute_ratio = brute_dam / (limb_loss_threshold * 2)
burn_ratio = burn_dam / (limb_loss_threshold * 2)
// 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(var/clean, var/disintegrate = DROPLIMB_EDGE, var/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!"),\
"<span class='moderate'><b><font size=2>Your [src.name] goes flying off!</font></b></span>",\
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!"),\
"<span class='moderate'><b><font size=2>Your [src.name] flashes away into ashes!</font></b></span>",\
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]!"),\
"<span class='moderate'><b><font size=3>Your [src.name] explodes[gore]!</font></b></span>",\
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/parent_organ = 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(parent_organ)
var/datum/wound/lost_limb/W = new(src, disintegrate, clean)
if(clean)
parent_organ.wounds |= W
parent_organ.update_damages()
else
var/obj/item/organ/external/stump/stump = new(victim, 0, src)
if(status & ORGAN_ROBOT)
stump.robotize()
stump.wounds |= W
victim.organs |= stump
stump.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
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()
INVOKE_ASYNC(gore, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 4)
for(var/obj/item/organ/I in internal_organs)
I.removed()
if(istype(loc,/turf))
INVOKE_ASYNC(I, 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)
if(I.w_class <= 2)
qdel(I)
continue
I.forceMove(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.bandaged = 1
return rval
/obj/item/organ/external/proc/salve()
var/rval = 0
for(var/datum/wound/W in wounds)
rval |= !W.salved
W.salved = 1
return rval
/obj/item/organ/external/proc/disinfect()
var/rval = 0
for(var/datum/wound/W in wounds)
rval |= !W.disinfected
W.disinfected = 1
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("<font size=2>You hear a loud cracking sound coming from \the [owner]!</font>"),\
SPAN_DANGER("<font size=3>Something feels like it [message] in your [name]!</font>"),\
"You hear a sickening crack!")
if(owner.species && owner.can_feel_pain())
owner.emote("scream")
owner.flash_strong_pain()
playsound(src.loc, /singleton/sound_category/fracture_sound, 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 > 1)
return FALSE
if(burn_ratio > 1)
return FALSE
return ..()
/obj/item/organ/external/proc/is_malfunctioning()
if(BP_IS_ROBOTIC(src) && (brute_ratio + burn_ratio) >= 0.3 && 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(var/obj/item/W, var/silent = 0, var/supplied_message)
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!"))
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)
else
W.forceMove(owner)
/obj/item/organ/external/removed(var/mob/living/user, var/ignore_children = 0)
if(!owner)
return
var/is_robotic = status & ORGAN_ROBOT
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 < 3)
implant.forceMove(get_turf(victim.loc))
else
implant.forceMove(src)
implants.Cut()
// Attached organs also fly off.
if(!ignore_children)
for(var/obj/item/organ/external/O in children)
O.removed()
if(O)
O.forceMove(src)
for(var/obj/item/I in O.contents)
I.forceMove(src)
// Grab all the internal giblets too.
for(var/obj/item/organ/organ in internal_organs)
organ.removed()
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.
//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()) this_wound_desc = "bleeding [this_wound_desc]"
if(W.bandaged == 1)
this_wound_desc = "bandaged [this_wound_desc]"
else if(W.bandaged != 0)
this_wound_desc = "[W.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(bits.len)
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()
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