mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-20 15:36:34 +01:00
1600 lines
52 KiB
Plaintext
1600 lines
52 KiB
Plaintext
/obj/item/organ/external
|
|
name = "external"
|
|
max_damage = 0
|
|
min_broken_damage = 30
|
|
dir = SOUTH
|
|
organ_tag = "limb"
|
|
decays = FALSE
|
|
|
|
//* Behaviour *//
|
|
/// this covers things like 'can this limb be injected' or 'can this limb be healed'
|
|
var/behaviour_flags = NONE
|
|
|
|
//* Coverage *//
|
|
/// body_cover_flags that count as covering us
|
|
var/body_part_flags = NONE
|
|
|
|
//* Damage
|
|
/// https://www.desmos.com/calculator/eyn1lj5gq7
|
|
/// intensifier value used for inverse-damage softcap
|
|
var/damage_softcap_intensifier = 0
|
|
|
|
//* Physiology *//
|
|
/// local physiology holder
|
|
var/datum/local_physiology/physiology
|
|
/// local-ized physiology modifiers - these are always on us, even when we are mob-less
|
|
var/list/datum/physiology_modifier/physiology_modifiers
|
|
|
|
//* Wounds *//
|
|
/// Wound datum list.
|
|
var/list/wounds
|
|
/// Number of wounds we have - some wounds like bruises will collate into one datum representing all of them.
|
|
var/wound_tally
|
|
|
|
//* ## STRINGS
|
|
/// Fracture description if any.
|
|
var/broken_description
|
|
/// Modifier used for generating the on-mob damage overlay for this limb.
|
|
var/damage_state = "00"
|
|
|
|
|
|
//* ## DAMAGE VARS
|
|
/// Multiplier for incoming brute damage.
|
|
var/brute_mod = 1
|
|
/// As above for burn.
|
|
var/burn_mod = 1
|
|
/// EMP damage multiplier
|
|
var/emp_mod = 1
|
|
/// Actual current brute damage.
|
|
var/brute_dam = 0
|
|
/// Actual current burn damage.
|
|
var/burn_dam = 0
|
|
/// Used in healing/processing calculations.
|
|
var/last_dam = -1
|
|
|
|
/// If damage done to this organ spreads to connected organs.
|
|
var/spread_dam = FALSE
|
|
/// If a needle has a chance to fail to penetrate.
|
|
var/thick_skin = FALSE
|
|
/// If a prosthetic limb is emagged, it will detonate when it fails.
|
|
var/sabotaged = FALSE
|
|
|
|
/// Chance of missing.
|
|
var/base_miss_chance = 20
|
|
|
|
|
|
//* ## APPEARANCE VARS
|
|
/// Snowflake warning, reee. Used for slime limbs.
|
|
var/nonsolid
|
|
/// Also for slimes. Used for transparent limbs.
|
|
var/transparent = 0
|
|
/// Icon state base.
|
|
var/icon_name = null
|
|
/// Used in mob overlay layering calculations.
|
|
var/icon_position = 0
|
|
/// Used when caching robolimb icons.
|
|
var/model
|
|
/// Used to force override of species-specific limb icons (for prosthetics). Also used for any limbs chopped from a simple mob, and then attached to humans.
|
|
var/force_icon
|
|
/// Used to force the override of the icon-key generated using the species. Must be used in tandem with the above.
|
|
var/force_icon_key
|
|
/// Cached icon for use in mob overlays.
|
|
var/icon/mob_icon
|
|
/// Whether or not the icon state appends a gender.
|
|
var/gendered_icon = FALSE
|
|
/// Skin tone.
|
|
var/s_tone
|
|
/// Skin colour
|
|
var/list/s_col
|
|
/// How the skin colour is applied.
|
|
var/s_col_blend = ICON_ADD
|
|
/// Hair colour
|
|
var/list/h_col
|
|
/// Icon blend for body hair if any.
|
|
var/body_hair
|
|
var/mob/living/applied_pressure
|
|
/// Markings (body_markings) to apply to the icon
|
|
var/list/markings = list()
|
|
|
|
//* ## STRUCTURAL VARS
|
|
/// Master-limb.
|
|
var/obj/item/organ/external/parent
|
|
/// Sub-limbs.
|
|
var/list/children = list()
|
|
/// Internal organs of this body part
|
|
var/list/internal_organs = list()
|
|
/// Implanted entities.
|
|
/// * Not necessarily `/obj/item/implant`, or even an `/obj/item`.
|
|
var/list/implants = list()
|
|
/// Relative size of the organ.
|
|
var/organ_rel_size = 25
|
|
var/atom/movable/splinted
|
|
|
|
//* ## JOINT/STATE VARS
|
|
/// It would be more appropriate if these two were named "affects_grasp" and "affects_stand" at this point
|
|
var/can_grasp
|
|
/// Modifies stance tally/ability to stand.
|
|
var/can_stand
|
|
/// Scarred/burned beyond recognition.
|
|
var/disfigured = FALSE
|
|
/// Impossible to amputate.
|
|
var/cannot_amputate
|
|
/// Impossible to fracture.
|
|
var/cannot_break
|
|
/// Impossible to gib, distinct from amputation.
|
|
var/cannot_gib
|
|
/// Descriptive string used in dislocation.
|
|
var/joint = "joint"
|
|
/// Descriptive string used in amputation.
|
|
var/amputation_point
|
|
/// If you target a joint, you can dislocate the limb, impairing it's usefulness and causing pain.
|
|
var/dislocated = FALSE
|
|
/// Needs to be opened with a saw to access the organs.
|
|
var/encased
|
|
|
|
|
|
//* ## SURGERY VARS
|
|
var/open = FALSE
|
|
var/stage = FALSE
|
|
var/cavity = FALSE
|
|
|
|
/// Surgical repair stage for burn.
|
|
var/burn_stage = 0
|
|
/// Surgical repair stage for brute.
|
|
var/brute_stage = 0
|
|
|
|
/// HUD element variable, see organ_icon.dm get_damage_hud_image()
|
|
var/image/hud_damage_image
|
|
|
|
/// makes this dumb as fuck mechanic slightly less awful - records queued syringe infections instead of a spawn()
|
|
var/syringe_infection_queued
|
|
|
|
/obj/item/organ/external/Initialize(mapload)
|
|
init_local_physiology()
|
|
. = ..(mapload, FALSE)
|
|
if(istype(owner))
|
|
replaced(owner)
|
|
sync_colour_to_human(owner)
|
|
addtimer(CALLBACK(src, PROC_REF(get_icon)), 1)
|
|
|
|
/obj/item/organ/external/Destroy()
|
|
if(parent)
|
|
if(parent.children)
|
|
parent.children -= src
|
|
parent = null
|
|
|
|
if(children)
|
|
for(var/obj/item/organ/external/C in children)
|
|
qdel(C)
|
|
children = null
|
|
|
|
if(internal_organs)
|
|
for(var/obj/item/organ/O in internal_organs)
|
|
qdel(O)
|
|
internal_organs = null
|
|
|
|
if(splinted && splinted.loc == src)
|
|
qdel(splinted)
|
|
splinted = null
|
|
|
|
if(istype(owner))
|
|
owner.organs -= src
|
|
owner.organs_by_name[organ_tag] = null
|
|
owner.organs_by_name -= organ_tag
|
|
while(null in owner.organs)
|
|
owner.organs -= null
|
|
|
|
QDEL_LIST_NULL(implants)
|
|
|
|
return ..()
|
|
|
|
/obj/item/organ/external/emp_act(severity)
|
|
if(!(robotic >= ORGAN_ROBOT))
|
|
return
|
|
var/burn_damage = 0
|
|
switch(severity)
|
|
if (1)
|
|
burn_damage += rand(10, 12)
|
|
if (2)
|
|
burn_damage += rand(6.5, 8)
|
|
if(3)
|
|
burn_damage += rand(4, 8)
|
|
if(4)
|
|
burn_damage += rand(2, 5)
|
|
|
|
if(burn_damage)
|
|
inflict_bodypart_damage(
|
|
burn = burn_damage * emp_mod,
|
|
weapon_descriptor = "electromagnetic overload",
|
|
)
|
|
|
|
/obj/item/organ/external/attack_self(mob/user, datum/event_args/actor/actor)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
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))
|
|
user.put_in_hands(I)
|
|
user.visible_message("<span class='danger'>\The [user] rips \the [I] out of \the [src]!</span>")
|
|
return //no eating the limb until everything's been removed
|
|
return ..()
|
|
|
|
/obj/item/organ/external/examine()
|
|
. = ..()
|
|
if(in_range(usr, src) || istype(usr, /mob/observer/dead))
|
|
for(var/obj/item/I in contents)
|
|
if(istype(I, /obj/item/organ))
|
|
continue
|
|
. += "<span class='danger'>There is \a [I] sticking out of it.</span>"
|
|
return
|
|
|
|
/obj/item/organ/external/attackby(obj/item/W as obj, mob/living/user as mob)
|
|
switch(stage)
|
|
if(0)
|
|
if(istype(W,/obj/item/surgical/scalpel))
|
|
user.visible_message("<span class='danger'><b>[user]</b> cuts [src] open with [W]!</span>")
|
|
stage++
|
|
return
|
|
if(1)
|
|
if(istype(W,/obj/item/surgical/retractor))
|
|
user.visible_message("<span class='danger'><b>[user]</b> cracks [src] open like an egg with [W]!</span>")
|
|
stage++
|
|
return
|
|
if(2)
|
|
if(istype(W,/obj/item/surgical/hemostat))
|
|
if(contents.len)
|
|
var/obj/item/removing = pick(contents)
|
|
removing.forceMove(get_turf(user.loc))
|
|
user.put_in_hands(removing)
|
|
user.visible_message("<span class='danger'><b>[user]</b> extracts [removing] from [src] with [W]!</span>")
|
|
else
|
|
user.visible_message("<span class='danger'><b>[user]</b> fishes around fruitlessly in [src] with [W].</span>")
|
|
return
|
|
..()
|
|
|
|
/obj/item/organ/external/proc/is_dislocated()
|
|
if(dislocated > 0)
|
|
return 1
|
|
if(is_parent_dislocated())
|
|
return 1//if any parent is dislocated, we are considered dislocated as well
|
|
return 0
|
|
|
|
/obj/item/organ/external/proc/is_parent_dislocated()
|
|
var/obj/item/organ/external/O = parent
|
|
while(O && O.dislocated != -1)
|
|
if(O.dislocated == 1)
|
|
return 1
|
|
O = O.parent
|
|
return 0
|
|
|
|
|
|
/obj/item/organ/external/proc/dislocate()
|
|
if(dislocated == -1)
|
|
return
|
|
|
|
dislocated = 1
|
|
if(istype(owner))
|
|
add_verb(owner, /mob/living/carbon/human/proc/relocate)
|
|
|
|
/obj/item/organ/external/proc/relocate()
|
|
if(dislocated == -1)
|
|
return
|
|
|
|
dislocated = 0
|
|
if(istype(owner))
|
|
owner.shock_stage += 20
|
|
|
|
//check to see if we still need the verb
|
|
for(var/obj/item/organ/external/limb in owner.organs)
|
|
if(limb.dislocated == 1)
|
|
return
|
|
remove_verb(owner, /mob/living/carbon/human/proc/relocate)
|
|
|
|
/obj/item/organ/external/update_health()
|
|
damage = min(max_damage, (brute_dam + burn_dam))
|
|
|
|
/obj/item/organ/external/replaced(var/mob/living/carbon/human/target)
|
|
owner = target
|
|
forceMove(owner)
|
|
|
|
// todo: removed() is not necessarily reliable.
|
|
for(var/datum/physiology_modifier/modifier as anything in owner.physiology_modifiers)
|
|
// todo: check biology
|
|
physiology.apply(modifier)
|
|
|
|
if(istype(owner))
|
|
owner.organs_by_name[organ_tag] = src
|
|
owner.organs |= src
|
|
for(var/obj/item/organ/organ in src)
|
|
organ.replaced(owner,src)
|
|
|
|
if(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
|
|
parent.cure_specific_wound(/datum/wound/lost_limb)
|
|
|
|
/****************************************************
|
|
DAMAGE PROCS
|
|
****************************************************/
|
|
|
|
/obj/item/organ/external/proc/is_damageable(check_damage_cap)
|
|
// todo: rework
|
|
if(check_damage_cap)
|
|
//Continued damage to vital organs can kill you, and robot organs don't count towards total damage so no need to cap them.
|
|
// todo: this is absolutely fucking stupid, rework asap.
|
|
if(vital || (robotic >= ORGAN_ROBOT) || brute_dam + burn_dam < max_damage)
|
|
return TRUE
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* process incoming damage
|
|
*
|
|
* @params
|
|
* * brute - brute damage to take
|
|
* * burn - burn damage to take
|
|
* * damage_mode - DAMAG_EMODE_* flags for the form of this damage
|
|
* * weapon descriptor - a string describing how it happened ("flash burns", "multiple precision cuts", etc)
|
|
* * defer_host_updates - update health / perform damage checks? this is only for owner updates, not self updates!!
|
|
*/
|
|
/obj/item/organ/external/proc/inflict_bodypart_damage(brute, burn, damage_mode, weapon_descriptor, defer_host_updates)
|
|
// todo: get rid of this shit, should be physiology
|
|
// legacy: brute/burnmod
|
|
brute = round(brute * brute_mod, 0.1)
|
|
burn = round(burn * burn_mod, 0.1)
|
|
|
|
// todo: better way to godmode
|
|
if(src.owner?.status_flags & STATUS_GODMODE)
|
|
return 0
|
|
|
|
if(!brute && !burn)
|
|
return 0
|
|
|
|
// todo: this is awful
|
|
var/sharp = damage_mode & DAMAGE_MODE_SHARP
|
|
var/edge = damage_mode & DAMAGE_MODE_EDGE
|
|
// cache owner incase we get detached
|
|
// todo: this is awful
|
|
var/mob/living/carbon/owner = src.owner
|
|
|
|
// todo: lol this is shit
|
|
// legacy: organ damage on high damage
|
|
if(internal_organs && (brute_dam >= max_damage || (((sharp && brute >= 5) || brute >= 10) && prob(5))))
|
|
// Damage an internal organ
|
|
if(internal_organs && internal_organs.len)
|
|
var/obj/item/organ/I = pick(internal_organs)
|
|
brute *= 0.5
|
|
I.take_damage(brute)
|
|
|
|
// todo: lol this is shit
|
|
// legacy: jostle if broken
|
|
if(is_broken() && brute && !(damage_mode & DAMAGE_MODE_GRADUAL) && owner)
|
|
jostle_bone(brute)
|
|
if(organ_can_feel_pain() && IS_CONSCIOUS(owner) && prob(40))
|
|
spawn(-1)
|
|
owner.emote_nosleep("scream") //getting hit on broken hand hurts
|
|
|
|
// todo: optimization
|
|
// legacy: autopsy data
|
|
if(weapon_descriptor)
|
|
add_autopsy_data(weapon_descriptor, brute + burn)
|
|
|
|
//* LEGACY BELOW
|
|
|
|
var/can_cut = (sharp) && (robotic < ORGAN_ROBOT)
|
|
|
|
// If the limbs can break, make sure we don't exceed the maximum damage a limb can take before breaking
|
|
// Non-vital organs are limited to max_damage. You can't kill someone by bludeonging their arm all the way to 200 -- you can
|
|
// push them faster into paincrit though, as the additional damage is converted into shock.
|
|
if(brute)
|
|
var/can_inflict_brute = max(0, max_damage - brute_dam)
|
|
if(can_inflict_brute >= brute)
|
|
if(can_cut)
|
|
if(sharp && !edge)
|
|
create_wound( WOUND_TYPE_PIERCE, brute )
|
|
else
|
|
create_wound( WOUND_TYPE_CUT, brute )
|
|
else
|
|
create_wound( WOUND_TYPE_BRUISE, brute )
|
|
else if(!(damage_mode & DAMAGE_MODE_NO_OVERFLOW))
|
|
var/overflow_brute = brute - can_inflict_brute
|
|
// keep allowing it, but, diminishing returns
|
|
var/damage_anyways_brute = !(damage_mode & DAMAGE_MODE_NO_OVERFLOW) && ( \
|
|
overflow_brute * min(1, 1 / ((max(brute_dam, max_damage) + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage))) \
|
|
)
|
|
overflow_brute -= damage_anyways_brute
|
|
if(can_cut)
|
|
if(sharp && !edge)
|
|
create_wound( WOUND_TYPE_PIERCE, damage_anyways_brute )
|
|
else
|
|
create_wound( WOUND_TYPE_CUT, damage_anyways_brute )
|
|
else
|
|
create_wound( WOUND_TYPE_BRUISE, damage_anyways_brute )
|
|
// rest goes into shock
|
|
if(owner)
|
|
owner.shock_stage += overflow_brute * 0.33
|
|
if(burn)
|
|
var/can_inflict_burn = max(0, max_damage - burn_dam)
|
|
if(can_inflict_burn >= burn)
|
|
create_wound( WOUND_TYPE_BURN, burn )
|
|
else
|
|
var/overflow_burn = burn - can_inflict_burn
|
|
var/damage_anyways_burn = !(damage_mode & DAMAGE_MODE_NO_OVERFLOW) && ( \
|
|
overflow_burn * min(1, 1 / ((max(burn_dam, max_damage) + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage))) \
|
|
)
|
|
overflow_burn -= damage_anyways_burn
|
|
create_wound(WOUND_TYPE_BURN, damage_anyways_burn + can_inflict_burn)
|
|
// rest goes into shock
|
|
if(owner)
|
|
owner.shock_stage += overflow_burn * 0.33
|
|
|
|
// sync the organ's damage with its wounds
|
|
update_damages()
|
|
|
|
// break it if needed
|
|
// todo: shit code lmao
|
|
if(brute_dam > min_broken_damage && prob((brute * ((brute_dam - min_broken_damage) / min_broken_damage)) * 2))
|
|
fracture()
|
|
|
|
//If limb took enough damage, try to cut or tear it off
|
|
if(!(damage_mode & DAMAGE_MODE_GRADUAL) && !is_stump() && !cannot_amputate && ((brute_dam > max_damage) || (burn_dam > max_damage)))
|
|
//organs can come off in three cases
|
|
//1. If the damage source is edge_eligible and the brute damage dealt exceeds the edge threshold, then the organ is cut off.
|
|
//2. If the damage amount dealt exceeds the disintegrate threshold, the organ is completely obliterated.
|
|
//3. If the organ has already reached or would be put over it's max damage amount (currently redundant),
|
|
// and the brute damage dealt exceeds the tearoff threshold, the organ is torn off.
|
|
|
|
//Check edge eligibility
|
|
//* edge eligibility disabled; organs should optimally not reqiure the item reference and should instead
|
|
//* get descriptors (damage, damage mode, etc) of the inbound attack.
|
|
var/edge_eligible = edge
|
|
// var/edge_eligible = 0
|
|
// if(edge)
|
|
// if(istype(used_weapon,/obj/item))
|
|
// var/obj/item/W = used_weapon
|
|
// if(W.w_class >= w_class)
|
|
// edge_eligible = 1
|
|
// else
|
|
// edge_eligible = 1
|
|
|
|
if(nonsolid && damage >= max_damage)
|
|
droplimb(TRUE, DROPLIMB_EDGE)
|
|
else if (robotic >= ORGAN_NANOFORM && damage >= max_damage)
|
|
droplimb(TRUE, DROPLIMB_BURN)
|
|
else if(edge_eligible && brute >= max_damage / DROPLIMB_THRESHOLD_EDGE && prob(brute))
|
|
droplimb(0, DROPLIMB_EDGE)
|
|
else if((burn >= max_damage / DROPLIMB_THRESHOLD_DESTROY) && prob(burn*0.33))
|
|
droplimb(0, DROPLIMB_BURN)
|
|
else if((brute >= max_damage / DROPLIMB_THRESHOLD_DESTROY && prob(brute)))
|
|
droplimb(0, DROPLIMB_BLUNT)
|
|
else if(brute >= max_damage / DROPLIMB_THRESHOLD_TEAROFF && prob(brute*0.33))
|
|
droplimb(0, DROPLIMB_EDGE)
|
|
//* damage spreading disabled; the attacking weapon should handle this if necessary.
|
|
// else if(spread_dam && owner && parent && (brute_overflow || burn_overflow) && (brute_overflow >= 5 || burn_overflow >= 5) && !permutation) //No infinite damage loops.
|
|
// var/brute_third = brute_overflow * 0.33
|
|
// var/burn_third = burn_overflow * 0.33
|
|
// if(children && children.len)
|
|
// var/brute_on_children = brute_third / children.len
|
|
// var/burn_on_children = burn_third / children.len
|
|
// spawn()
|
|
// for(var/obj/item/organ/external/C in children)
|
|
// if(!C.is_stump())
|
|
// C.take_damage(brute_on_children, burn_on_children, 0, 0, null, forbidden_limbs, 1) //Splits the damage to each individual 'child', incase multiple exist.
|
|
// parent.take_damage(brute_third, burn_third, 0, 0, null, forbidden_limbs, 1)
|
|
|
|
//* LEGACY ABOVE
|
|
|
|
if(!defer_host_updates)
|
|
owner?.update_health()
|
|
|
|
update_icon()
|
|
|
|
/obj/item/organ/external/proc/heal_damage(brute, burn, internal = 0, robo_repair = 0)
|
|
if(robotic >= ORGAN_ROBOT && !robo_repair)
|
|
return
|
|
|
|
//Heal damage on the individual wounds
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(brute == 0 && burn == 0)
|
|
break
|
|
|
|
// heal brute damage
|
|
if(W.wound_type == WOUND_TYPE_BURN)
|
|
burn = W.heal_damage(burn)
|
|
else
|
|
brute = W.heal_damage(brute)
|
|
|
|
if(internal)
|
|
status &= ~ORGAN_BROKEN
|
|
|
|
//Sync the organ's damage with its wounds
|
|
src.update_damages()
|
|
src.process_wounds() // todo: this should not be here - this has side effects of processing.
|
|
owner.update_health()
|
|
|
|
var/result = update_icon()
|
|
return result
|
|
|
|
/// Helper proc used by various tools for repairing robot limbs
|
|
/obj/item/organ/external/proc/robo_repair(repair_amount, damage_type, damage_desc, obj/item/tool, mob/living/user)
|
|
if((src.robotic < ORGAN_ROBOT))
|
|
return FALSE
|
|
|
|
var/damage_amount
|
|
switch(damage_type)
|
|
if(DAMAGE_TYPE_BRUTE)
|
|
damage_amount = brute_dam
|
|
if(DAMAGE_TYPE_BURN)
|
|
damage_amount = burn_dam
|
|
if("omni")
|
|
damage_amount = max(brute_dam,burn_dam)
|
|
else
|
|
return FALSE
|
|
|
|
if(!damage_amount)
|
|
to_chat(user, SPAN_NOTICE("Nothing to fix!"))
|
|
return FALSE
|
|
|
|
// Makes robotic limb damage scalable.
|
|
if(brute_dam + burn_dam >= min_broken_damage)
|
|
to_chat(user, SPAN_DANGER("The damage is far too severe to patch over externally."))
|
|
return FALSE
|
|
|
|
if(user == src.owner)
|
|
if(owner.get_hand_organ(owner.get_held_index(tool)) == src)
|
|
to_chat(user, SPAN_WARNING("You can't reach your [src] while holding [tool] in the same hand!"))
|
|
return FALSE
|
|
|
|
user.setClickCooldownLegacy(user.get_attack_speed_legacy(tool))
|
|
if(!do_mob(user, owner, 10))
|
|
to_chat(user, SPAN_WARNING("You must stand still to do that."))
|
|
return FALSE
|
|
|
|
switch(damage_type)
|
|
if(DAMAGE_TYPE_BRUTE)
|
|
src.heal_damage(repair_amount, 0, 0, 1)
|
|
if(DAMAGE_TYPE_BURN)
|
|
src.heal_damage(0, repair_amount, 0, 1)
|
|
if("omni")
|
|
src.heal_damage(repair_amount, repair_amount, 0, 1)
|
|
|
|
if(damage_desc)
|
|
if(user == src.owner)
|
|
var/datum/gender/T = GLOB.gender_datums[user.get_visible_gender()]
|
|
user.visible_message(SPAN_NOTICE("\The [user] patches [damage_desc] on [T.his] [src.name] with [tool]."))
|
|
else
|
|
user.visible_message(SPAN_NOTICE("\The [user] patches [damage_desc] on [owner]'s [src.name] with [tool]."))
|
|
|
|
return TRUE
|
|
|
|
/obj/item/organ/external/rejuvenate()
|
|
..()
|
|
brute_dam = 0
|
|
burn_dam = 0
|
|
wounds = null
|
|
wound_tally = 0
|
|
|
|
/**
|
|
* This function completely restores a damaged organ to perfect condition.
|
|
*/
|
|
/obj/item/organ/external/rejuvenate_legacy(ignore_prosthetic_prefs)
|
|
rejuvenate()
|
|
damage_state = "00"
|
|
status = 0
|
|
|
|
// handle internal organs
|
|
for(var/obj/item/organ/current_organ in internal_organs)
|
|
current_organ.rejuvenate_legacy(ignore_prosthetic_prefs)
|
|
|
|
// remove embedded objects and drop them on the floor
|
|
for(var/obj/implanted_object in implants)
|
|
if(!istype(implanted_object,/obj/item/implant) && !istype(implanted_object,/obj/item/nif)) // We don't want to remove REAL implants. Just shrapnel etc.
|
|
implanted_object.forceMove(get_turf(src))
|
|
implants -= implanted_object
|
|
|
|
if(owner && !ignore_prosthetic_prefs)
|
|
if(owner.client && owner.client.prefs && owner.client.prefs.real_name == owner.real_name)
|
|
var/status = owner.client.prefs.organ_data[organ_tag]
|
|
if(status == "amputated")
|
|
remove_rejuv()
|
|
else if(status == "cyborg")
|
|
var/robodata = owner.client.prefs.rlimb_data[organ_tag]
|
|
if(robodata)
|
|
robotize(robodata, null, null, TRUE)
|
|
else
|
|
robotize()
|
|
owner.update_health()
|
|
|
|
/obj/item/organ/external/remove_rejuv()
|
|
if(owner)
|
|
owner.organs -= src
|
|
owner.organs_by_name[organ_tag] = null
|
|
owner.organs_by_name -= organ_tag
|
|
while(null in owner.organs) owner.organs -= null
|
|
if(children && children.len)
|
|
for(var/obj/item/organ/external/E in children)
|
|
E.remove_rejuv()
|
|
children.Cut()
|
|
for(var/obj/item/organ/internal/I in internal_organs)
|
|
I.remove_rejuv()
|
|
..()
|
|
|
|
/****************************************************
|
|
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()
|
|
return (status & ORGAN_CUT_AWAY) || ((status & ORGAN_BROKEN) && !splinted)
|
|
|
|
//Determines if we even need to process this organ.
|
|
/obj/item/organ/external/proc/need_process()
|
|
if(status & (ORGAN_CUT_AWAY|ORGAN_BLEEDING|ORGAN_BROKEN|ORGAN_DESTROYED|ORGAN_DEAD|ORGAN_MUTATED))
|
|
return 1
|
|
if(brute_dam || burn_dam) //Robot limbs don't autoheal and thus don't need to process when damaged. Aside from medichines.
|
|
return 1
|
|
if(last_dam != brute_dam + burn_dam) // Process when we are fully healed up.
|
|
last_dam = brute_dam + burn_dam
|
|
return 1
|
|
else
|
|
last_dam = brute_dam + burn_dam
|
|
if(germ_level && (owner && !IS_DEAD(owner)))
|
|
return 1
|
|
if(length(wounds))
|
|
return TRUE
|
|
return 0
|
|
|
|
/obj/item/organ/external/tick_life(dt)
|
|
. = ..()
|
|
|
|
if(owner)
|
|
//Dismemberment
|
|
//if(parent && parent.is_stump()) //should never happen
|
|
// warning("\The [src] ([src.type]) belonging to [owner] ([owner.type]) was attached to a stump")
|
|
// remove()
|
|
// return
|
|
|
|
// Process wounds, doing healing etc. Only do this every few ticks to save processing power
|
|
process_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)
|
|
|
|
//Infections
|
|
update_germs()
|
|
|
|
//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 spaceacillin 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(robotic >= ORGAN_ROBOT || (owner.species && (owner.species.species_flags & IS_PLANT || (owner.species.species_flags & NO_INFECT)))) //Robotic limbs shouldn't be infected, nor should nonexistant limbs.
|
|
germ_level = 0
|
|
return
|
|
|
|
if(owner && !IS_DEAD(owner))
|
|
return
|
|
|
|
if(owner.bodytemperature >= 170) //cryo stops germs from moving and doing their bad stuffs
|
|
//** Syncing germ levels with external wounds
|
|
handle_germ_sync()
|
|
|
|
// removal temporary, pending health rework
|
|
//** 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 = owner.chem_effects[CE_ANTIBIOTIC]
|
|
for(var/datum/wound/W as anything in wounds)
|
|
//Open wounds can become infected
|
|
if (owner.germ_level > W.germ_level && W.infection_check())
|
|
W.germ_level++
|
|
|
|
if (antibiotics < ANTIBIO_NORM)
|
|
for(var/datum/wound/W as anything in wounds)
|
|
//Infected wounds raise the organ's germ level
|
|
if (W.germ_level > germ_level)
|
|
germ_level++
|
|
break //limit increase to a maximum of one per second
|
|
|
|
/obj/item/organ/external/handle_germ_effects()
|
|
. = ..() //May be null or an infection level, if null then no specific processing needed here
|
|
if(!.)
|
|
return
|
|
|
|
var/antibiotics = owner.chem_effects[CE_ANTIBIOTIC]
|
|
|
|
if(. >= 2 && antibiotics < ANTIBIO_NORM) //INFECTION_LEVEL_TWO
|
|
//spread the infection to internal organs
|
|
var/obj/item/organ/target_organ = null //make internal organs become infected one at a time instead of all at once
|
|
for (var/obj/item/organ/I in internal_organs)
|
|
if (I.germ_level > 0 && I.germ_level < min(germ_level, INFECTION_LEVEL_TWO)) //once the organ reaches whatever we can give it, or level two, switch to a different one
|
|
if (!target_organ || I.germ_level > target_organ.germ_level) //choose the organ with the highest germ_level
|
|
target_organ = I
|
|
|
|
if (!target_organ)
|
|
//figure out which organs we can spread germs to and pick one at random
|
|
var/list/candidate_organs = list()
|
|
for (var/obj/item/organ/I in internal_organs)
|
|
if (I.germ_level < germ_level)
|
|
candidate_organs |= I
|
|
if (candidate_organs.len)
|
|
target_organ = pick(candidate_organs)
|
|
|
|
if (target_organ)
|
|
target_organ.germ_level++
|
|
|
|
//spread the infection to child and parent organs
|
|
if (children)
|
|
for (var/obj/item/organ/external/child in children)
|
|
if (child.germ_level < germ_level && (child.robotic < ORGAN_ROBOT))
|
|
if (child.germ_level < INFECTION_LEVEL_ONE*2 || prob(30))
|
|
child.germ_level++
|
|
|
|
if (parent)
|
|
if (parent.germ_level < germ_level && (parent.robotic < ORGAN_ROBOT))
|
|
if (parent.germ_level < INFECTION_LEVEL_ONE*2 || prob(30))
|
|
parent.germ_level++
|
|
|
|
if(. >= 3 && antibiotics < ANTIBIO_OD) //INFECTION_LEVEL_THREE
|
|
if (!(status & ORGAN_DEAD))
|
|
status |= ORGAN_DEAD
|
|
to_chat(owner, "<span class='notice'>You can't feel your [name] anymore...</span>")
|
|
owner.update_icons_body()
|
|
for (var/obj/item/organ/external/child in children)
|
|
child.germ_level += 110 //Burst of infection from a parent organ becoming necrotic
|
|
|
|
//Updating wounds. Handles wound natural I had some free spachealing, internal bleedings and infections
|
|
/obj/item/organ/external/proc/process_wounds()
|
|
|
|
if((robotic >= ORGAN_ROBOT) || (species.species_flags & UNDEAD)) //Robotic and dead limbs don't heal or get worse.
|
|
for(var/datum/wound/W as anything in wounds) //Repaired wounds disappear though
|
|
if(W.damage <= 0) //and they disappear right away
|
|
wounds -= W //TODO: robot wounds for robot limbs
|
|
src.update_damages()
|
|
if (update_icon())
|
|
owner.update_damage_overlay(1)
|
|
return
|
|
|
|
for(var/datum/wound/W as anything in wounds)
|
|
// wounds can disappear after 10 minutes at the earliest
|
|
if(W.damage <= 0 && W.created + 10 * 10 * 60 <= world.time)
|
|
wounds -= W
|
|
continue
|
|
// let the GC handle the deletion of the wound
|
|
|
|
// Internal wounds get worse over time. Low temperatures (cryo) stop them.
|
|
if(W.internal && owner.bodytemperature >= 170)
|
|
var/bicardose = owner.reagents.get_reagent_amount("bicaridine")
|
|
var/inaprovaline = owner.reagents.get_reagent_amount("inaprovaline")
|
|
var/myeldose = owner.reagents.get_reagent_amount("myelamine")
|
|
if(!(W.can_autoheal() || (bicardose && inaprovaline) || myeldose)) //bicaridine and inaprovaline stop internal wounds from growing bigger with time, unless it is so small that it is already healing
|
|
W.open_wound(0.1)
|
|
|
|
owner.erase_blood(W.damage / 40)
|
|
if(prob(1))
|
|
owner.custom_pain("You feel a stabbing pain in your [name]!", 50)
|
|
|
|
// 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
|
|
|
|
// todo: config entry after more med refactors
|
|
// amount of healing is spread over all the wounds
|
|
heal_amt = heal_amt / (length(wounds) + 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 (update_icon())
|
|
owner.update_damage_overlay(1)
|
|
|
|
//Updates brute_damn and burn_damn from wound damages. Updates BLEEDING status.
|
|
/obj/item/organ/external/proc/update_damages()
|
|
wound_tally = 0
|
|
brute_dam = 0
|
|
burn_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 as anything in wounds)
|
|
if(!W.internal) //so IB doesn't count towards crit/paincrit
|
|
if(W.wound_type == WOUND_TYPE_BURN)
|
|
burn_dam += W.damage
|
|
else
|
|
brute_dam += W.damage
|
|
|
|
if(!(robotic >= ORGAN_ROBOT) && W.bleeding() && (H && H.should_have_organ(O_HEART)) && !(H.species.species_flags & NO_BLOOD))
|
|
W.bleed_timer--
|
|
status |= ORGAN_BLEEDING
|
|
|
|
clamped |= W.clamped
|
|
|
|
wound_tally += W.amount
|
|
|
|
//things tend to bleed if they are WOUND_TYPE_CUT OPEN
|
|
if (open && !clamped && (H && H.should_have_organ(O_HEART)))
|
|
status |= ORGAN_BLEEDING
|
|
|
|
update_health()
|
|
|
|
// new damage icon system
|
|
// adjusted to set damage_state to brute/burn code only (without r_name0 as before)
|
|
/obj/item/organ/external/update_icon()
|
|
var/n_is = damage_state_text()
|
|
if (n_is != damage_state)
|
|
damage_state = n_is
|
|
return 1
|
|
return 0
|
|
|
|
// 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
|
|
****************************************************/
|
|
|
|
//Handles dismemberment
|
|
/obj/item/organ/external/proc/droplimb(var/clean, var/disintegrate = DROPLIMB_EDGE, var/ignore_children = null)
|
|
|
|
if(cannot_amputate || !owner)
|
|
return
|
|
if(robotic >= ORGAN_NANOFORM)
|
|
disintegrate = DROPLIMB_BURN //Ashes will be fine
|
|
else if(disintegrate == DROPLIMB_EDGE && nonsolid)
|
|
disintegrate = DROPLIMB_BLUNT //splut
|
|
|
|
switch(disintegrate)
|
|
if(DROPLIMB_EDGE)
|
|
if(!clean)
|
|
var/gore_sound = "[(robotic >= ORGAN_ROBOT) ? "tortured metal" : "ripping tendons and flesh"]"
|
|
owner.visible_message(
|
|
"<span class='danger'>\The [owner]'s [src.name] flies off in an arc!</span>",\
|
|
"<span class='moderate'><b>Your [src.name] goes flying off!</b></span>",\
|
|
"<span class='danger'>You hear a terrible sound of [gore_sound].</span>")
|
|
if(DROPLIMB_BURN)
|
|
if(cannot_gib)
|
|
return
|
|
var/gore = "[(robotic >= ORGAN_ROBOT) ? "": " of burning flesh"]"
|
|
owner.visible_message(
|
|
"<span class='danger'>\The [owner]'s [src.name] flashes away into ashes!</span>",\
|
|
"<span class='moderate'><b>Your [src.name] flashes away into ashes!</b></span>",\
|
|
"<span class='danger'>You hear a crackling sound[gore].</span>")
|
|
if(DROPLIMB_BLUNT)
|
|
if(cannot_gib)
|
|
return
|
|
var/gore = "[(robotic >= ORGAN_ROBOT) ? "": " in shower of gore"]"
|
|
var/gore_sound = "[(status >= ORGAN_ROBOT) ? "rending sound of tortured metal" : "sickening splatter of gore"]"
|
|
owner.visible_message(
|
|
"<span class='danger'>\The [owner]'s [src.name] explodes[gore]!</span>",\
|
|
"<span class='moderate'><b>Your [src.name] explodes[gore]!</b></span>",\
|
|
"<span class='danger'>You hear the [gore_sound].</span>")
|
|
|
|
var/mob/living/carbon/human/victim = owner //Keep a reference for post-removed().
|
|
var/obj/item/organ/external/parent_organ = parent
|
|
|
|
var/use_flesh_colour = species?.get_flesh_colour(owner) ? species.get_flesh_colour(owner) : "#C80000"
|
|
var/use_blood_colour = species?.get_blood_colour(owner) ? species.get_blood_colour(owner) : "#C80000"
|
|
|
|
removed(null, ignore_children)
|
|
victim?.traumatic_shock += 60
|
|
|
|
if(parent_organ)
|
|
var/datum/wound/lost_limb/W = new (src, disintegrate, clean)
|
|
if(clean)
|
|
LAZYDISTINCTADD(parent_organ.wounds, W)
|
|
parent_organ.update_damages()
|
|
else
|
|
var/obj/item/organ/external/stump/stump = new (victim, 0, src)
|
|
if(robotic >= ORGAN_ROBOT)
|
|
stump.robotize()
|
|
LAZYDISTINCTADD(stump.wounds, W)
|
|
victim.organs |= stump
|
|
stump.update_damages()
|
|
|
|
spawn(1)
|
|
if(istype(victim))
|
|
victim.update_health()
|
|
victim.update_damage_overlay()
|
|
victim.update_icons_body()
|
|
else
|
|
victim.update_icons()
|
|
dir = 2
|
|
|
|
var/atom/droploc = victim.drop_location()
|
|
switch(disintegrate)
|
|
if(DROPLIMB_EDGE)
|
|
appearance_flags &= ~PIXEL_SCALE
|
|
compile_icon()
|
|
add_blood(victim)
|
|
var/matrix/M = matrix()
|
|
M.Turn(rand(180))
|
|
src.transform = M
|
|
if(!clean)
|
|
// Throw limb around.
|
|
if(src && istype(loc,/turf))
|
|
throw_at_old(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
|
|
dir = 2
|
|
if(DROPLIMB_BURN)
|
|
new /obj/effect/debris/cleanable/ash(droploc)
|
|
for(var/obj/item/I in src)
|
|
if(I.w_class > WEIGHT_CLASS_SMALL && !istype(I,/obj/item/organ))
|
|
I.forceMove(droploc)
|
|
qdel(src)
|
|
if(DROPLIMB_BLUNT)
|
|
var/obj/effect/debris/cleanable/blood/gibs/gore
|
|
if(robotic >= ORGAN_ROBOT)
|
|
gore = new /obj/effect/debris/cleanable/blood/gibs/robot(droploc)
|
|
else
|
|
gore = new /obj/effect/debris/cleanable/blood/gibs(droploc)
|
|
if(species)
|
|
gore.fleshcolor = use_flesh_colour
|
|
gore.basecolor = use_blood_colour
|
|
gore.update_icon()
|
|
|
|
gore.throw_at_old(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
|
|
|
|
for(var/obj/item/organ/I in internal_organs)
|
|
I.removed()
|
|
if(istype(loc,/turf))
|
|
I.throw_at_old(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
|
|
|
|
for(var/obj/item/I in src)
|
|
if(I.w_class <= WEIGHT_CLASS_SMALL)
|
|
qdel(I)
|
|
continue
|
|
I.forceMove(droploc)
|
|
I.throw_at_old(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5)
|
|
|
|
qdel(src)
|
|
|
|
/****************************************************
|
|
HELPERS
|
|
****************************************************/
|
|
|
|
/obj/item/organ/external/proc/is_stump()
|
|
return 0
|
|
|
|
/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_flags 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_item_to_ground(holder.handcuffed, INV_OP_FORCE)
|
|
if (holder.legcuffed && (body_part_flags 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_item_to_ground(holder.legcuffed, INV_OP_FORCE)
|
|
|
|
// checks if all wounds on the organ are bandaged
|
|
/obj/item/organ/external/proc/is_bandaged()
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(W.internal) continue
|
|
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 as anything in wounds)
|
|
if(W.internal) continue
|
|
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 as anything in wounds)
|
|
if(W.internal) continue
|
|
if(!W.disinfected)
|
|
return 0
|
|
return 1
|
|
|
|
/obj/item/organ/external/proc/bandage()
|
|
var/rval = 0
|
|
status &= ~ORGAN_BLEEDING
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(W.internal) continue
|
|
rval |= !W.bandaged
|
|
W.bandaged = 1
|
|
return rval
|
|
|
|
/obj/item/organ/external/proc/salve()
|
|
var/rval = 0
|
|
for(var/datum/wound/W as anything in wounds)
|
|
rval |= !W.salved
|
|
W.salved = 1
|
|
return rval
|
|
|
|
/obj/item/organ/external/proc/disinfect()
|
|
var/rval = 0
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(W.internal) continue
|
|
rval |= !W.disinfected
|
|
W.disinfected = 1
|
|
W.germ_level = 0
|
|
return rval
|
|
|
|
/obj/item/organ/external/proc/organ_clamp()
|
|
var/rval = 0
|
|
src.status &= ~ORGAN_BLEEDING
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(W.internal) continue
|
|
rval |= !W.clamped
|
|
W.clamped = 1
|
|
return rval
|
|
|
|
/obj/item/organ/external/proc/fracture()
|
|
if(robotic >= ORGAN_ROBOT)
|
|
return //ORGAN_BROKEN doesn't have the same meaning for robot limbs
|
|
if((status & ORGAN_BROKEN) || cannot_break)
|
|
return
|
|
|
|
if(owner)
|
|
owner.visible_message(\
|
|
"<span class='danger'>You hear a loud cracking sound coming from \the [owner].</span>",\
|
|
"<span class='danger'>Something feels like it shattered in your [name]!</span>",\
|
|
"<span class='danger'>You hear a sickening crack.</span>")
|
|
jostle_bone()
|
|
if(organ_can_feel_pain() && IS_CONSCIOUS(owner) && !isbelly(owner.loc))
|
|
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "scream")
|
|
|
|
playsound(src.loc, "fracture", 10, 1, -2)
|
|
status |= ORGAN_BROKEN
|
|
broken_description = pick("broken","fracture","hairline fracture")
|
|
|
|
// 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.
|
|
// TODO: consider moving this to a suit proc or process() or something during
|
|
// hardsuit rewrite.
|
|
|
|
if(!(splinted) && owner && istype(owner.wear_suit, /obj/item/clothing/suit/space))
|
|
var/obj/item/clothing/suit/space/suit = owner.wear_suit
|
|
suit.handle_fracture(owner, src)
|
|
|
|
return 1
|
|
|
|
/obj/item/organ/external/proc/mend_fracture()
|
|
if(robotic >= ORGAN_ROBOT)
|
|
return 0 //ORGAN_BROKEN doesn't have the same meaning for robot limbs
|
|
if(brute_dam > min_broken_damage)
|
|
return 0 //will just immediately fracture again
|
|
|
|
status &= ~ORGAN_BROKEN
|
|
return 1
|
|
|
|
/obj/item/organ/external/proc/apply_splint(var/atom/movable/splint)
|
|
if(!splinted)
|
|
splinted = splint
|
|
if(!applied_pressure)
|
|
applied_pressure = splint
|
|
return 1
|
|
return 0
|
|
|
|
/obj/item/organ/external/proc/remove_splint()
|
|
if(splinted)
|
|
if(splinted.loc == src)
|
|
splinted.dropInto(owner? owner.loc : src.loc)
|
|
if(applied_pressure == splinted)
|
|
applied_pressure = null
|
|
splinted = null
|
|
return 1
|
|
return 0
|
|
|
|
/obj/item/organ/external/robotize(var/company, var/skip_prosthetics = 0, var/keep_organs = 0, force)
|
|
//* SHITCODE ALERT: REFACTOR ORGANS ASAP; FORCE IS JUST SO PREFS WORK.
|
|
if(robotic >= ORGAN_ROBOT && !force)
|
|
return
|
|
|
|
..()
|
|
|
|
if(company)
|
|
model = company
|
|
var/datum/robolimb/R = GLOB.all_robolimbs[isnum(company) ? GLOB.all_robolimbs[company] : company]
|
|
if(!R || (species && (species.name in R.species_cannot_use)))
|
|
R = GLOB.basic_robolimb
|
|
if(R)
|
|
force_icon = R.icon
|
|
brute_mod = initial(brute_mod)
|
|
burn_mod = initial(brute_mod)
|
|
brute_mod *= R.robo_brute_mod
|
|
burn_mod *= R.robo_burn_mod
|
|
if(R.lifelike)
|
|
robotic = ORGAN_LIFELIKE
|
|
name = "[initial(name)]"
|
|
else if(R.modular_bodyparts == MODULAR_BODYPART_PROSTHETIC)
|
|
name = "prosthetic [initial(name)]"
|
|
else
|
|
name = "robotic [initial(name)]"
|
|
desc = "[R.desc] It looks like it was produced by [R.company]."
|
|
|
|
dislocated = -1
|
|
cannot_break = 1
|
|
min_broken_damage = ROBOLIMB_REPAIR_CAP // Makes robotic limb damage scalable
|
|
remove_splint()
|
|
get_icon()
|
|
unmutate()
|
|
|
|
for(var/obj/item/organ/external/T in children)
|
|
T.robotize(company, keep_organs = keep_organs)
|
|
|
|
if(owner)
|
|
|
|
if(!keep_organs)
|
|
for(var/obj/item/organ/thing in internal_organs)
|
|
if(istype(thing))
|
|
if(thing.vital)
|
|
continue
|
|
internal_organs -= thing
|
|
owner.internal_organs_by_name[thing.organ_tag] = null
|
|
owner.internal_organs_by_name -= thing.organ_tag
|
|
owner.internal_organs.Remove(thing)
|
|
qdel(thing)
|
|
|
|
while(null in owner.internal_organs)
|
|
owner.internal_organs -= null
|
|
owner.refresh_modular_limb_verbs()
|
|
return 1
|
|
|
|
//* ## VIRGO HOOK, TODO: Integrate this.
|
|
//Sideways override for nanoform limbs (ugh)
|
|
/obj/item/organ/external/robotize(var/company, var/skip_prosthetics = FALSE, var/keep_organs = FALSE, force)
|
|
var/original_robotic = robotic
|
|
if(original_robotic >= ORGAN_NANOFORM)
|
|
var/o_encased = encased
|
|
var/o_max_damage = max_damage
|
|
var/o_min_broken_damage = min_broken_damage
|
|
robotic = FALSE
|
|
. = ..(company = company, keep_organs = TRUE)
|
|
robotic = original_robotic
|
|
encased = o_encased
|
|
max_damage = o_max_damage
|
|
min_broken_damage = o_min_broken_damage
|
|
else
|
|
return ..()
|
|
|
|
/obj/item/organ/external/proc/mutate()
|
|
if(src.robotic >= ORGAN_ROBOT)
|
|
return
|
|
src.status |= ORGAN_MUTATED
|
|
if(owner) owner.update_icons_body()
|
|
|
|
/obj/item/organ/external/proc/unmutate()
|
|
src.status &= ~ORGAN_MUTATED
|
|
if(owner) owner.update_icons_body()
|
|
|
|
/obj/item/organ/external/proc/get_damage() //returns total damage
|
|
return (brute_dam+burn_dam) //could use max_damage?
|
|
|
|
/obj/item/organ/external/proc/has_infected_wound()
|
|
for(var/datum/wound/W as anything in wounds)
|
|
if(W.germ_level > INFECTION_LEVEL_ONE)
|
|
return 1
|
|
return 0
|
|
|
|
/obj/item/organ/external/proc/is_usable()
|
|
return !(status & (ORGAN_MUTATED|ORGAN_DEAD))
|
|
|
|
/obj/item/organ/external/proc/is_malfunctioning()
|
|
return ((robotic >= ORGAN_ROBOT) && (brute_dam + burn_dam) >= min_broken_damage*0.83 && prob(brute_dam + burn_dam)) // Makes robotic limb damage scalable
|
|
|
|
// TODO: rework embeds, this only works for tiny items right now as
|
|
// larger ones won't be removable!!
|
|
/obj/item/organ/external/proc/embed(var/obj/item/W, var/silent = 0)
|
|
if(!owner || loc != owner)
|
|
return
|
|
if(owner.species.reagent_tag == IS_SLIME)
|
|
create_wound( WOUND_TYPE_CUT, 15 ) //fixes proms being bugged into paincrit;instead whatever would embed now just takes a chunk out
|
|
src.visible_message("<font color='red'>[owner] has been seriously wounded by [W]!</font>")
|
|
W.add_blood(owner)
|
|
return 0
|
|
if(ismob(W.loc))
|
|
var/mob/M = W.loc
|
|
if(!M.can_unequip(W))
|
|
return
|
|
if(!silent)
|
|
owner.visible_message("<span class='danger'>\The [W] sticks in the wound!</span>")
|
|
implants += W
|
|
owner.embedded_flag = 1
|
|
// add_verb(owner, /mob/proc/yank_out_object)
|
|
if(!(owner.species.species_flags & NO_BLOOD))
|
|
W.add_blood(owner)
|
|
W.forceMove(owner)
|
|
|
|
/obj/item/organ/external/removed(var/mob/living/user, var/ignore_children = 0)
|
|
if(!owner)
|
|
return
|
|
owner.reconsider_inventory_slot_bodypart(organ_tag)
|
|
var/is_robotic = robotic >= ORGAN_ROBOT
|
|
var/mob/living/carbon/human/victim = owner
|
|
|
|
// todo: removed() is not necessarily reliable.
|
|
for(var/datum/physiology_modifier/modifier as anything in owner.physiology_modifiers)
|
|
// todo: check biology
|
|
if(!physiology.revert(modifier))
|
|
// todo: optimize?
|
|
rebuild_physiology()
|
|
for(var/datum/physiology_modifier/rebuilding as anything in owner.physiology_modifiers)
|
|
// todo: check biology
|
|
physiology.apply(rebuilding)
|
|
|
|
..()
|
|
|
|
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(victim.drop_location())
|
|
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[organ_tag] = null // Remove from owner's vars.
|
|
|
|
status |= ORGAN_CUT_AWAY //Checked during surgeries to reattach it
|
|
|
|
//Robotic limbs explode if sabotaged.
|
|
if(is_robotic && sabotaged)
|
|
victim.visible_message(
|
|
"<span class='danger'>\The [victim]'s [src.name] explodes violently!</span>",\
|
|
"<span class='danger'>Your [src.name] explodes!</span>",\
|
|
"<span class='danger'>You hear an explosion!</span>")
|
|
explosion(get_turf(owner),-1,-1,2,3)
|
|
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
|
|
spark_system.set_up(5, 0, victim)
|
|
spark_system.attach(owner)
|
|
spark_system.start()
|
|
spawn(10)
|
|
qdel(spark_system)
|
|
qdel(src)
|
|
|
|
victim.refresh_modular_limb_verbs()
|
|
victim.update_icons_body()
|
|
|
|
/obj/item/organ/external/proc/disfigure(var/type = "brute")
|
|
if (disfigured)
|
|
return
|
|
if(owner)
|
|
if(type == "brute")
|
|
owner.visible_message("<span class='danger'>You hear a sickening cracking sound coming from \the [owner]'s [name].</span>", \
|
|
"<span class='danger'>Your [name] becomes a mangled mess!</span>", \
|
|
"<span class='danger'>You hear a sickening crack.</span>")
|
|
else
|
|
owner.visible_message("<span class='danger'>\The [owner]'s [name] melts away, turning into mangled mess!</span>", \
|
|
"<span class='danger'>Your [name] melts away!</span>", \
|
|
"<span class='danger'>You hear a sickening sizzle.</span>")
|
|
disfigured = 1
|
|
|
|
/obj/item/organ/external/proc/jostle_bone(force)
|
|
if(!(status & ORGAN_BROKEN)) //intact bones stay still
|
|
return
|
|
if(brute_dam + force < min_broken_damage/5) //no papercuts moving bones
|
|
return
|
|
if(internal_organs.len && prob(brute_dam + force))
|
|
owner.custom_pain("A piece of bone in your [encased ? encased : name] moves painfully!", 50)
|
|
var/obj/item/organ/I = pick(internal_organs)
|
|
I.take_damage(rand(3,5))
|
|
|
|
/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(robotic >= ORGAN_ROBOT)
|
|
var/LL //Life-Like, aka only show that it's robotic in heavy damage
|
|
if(robotic >= ORGAN_LIFELIKE)
|
|
LL = 1
|
|
if(brute_dam)
|
|
switch(brute_dam)
|
|
if(0 to 20)
|
|
. += "some [LL ? "cuts" : "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 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
|
|
|
|
//Normal organic organ damage
|
|
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 as anything in wounds)
|
|
if(W.internal && !open) continue // can't see internal wounds
|
|
var/this_wound_desc = W.desc
|
|
|
|
if(W.wound_type == WOUND_TYPE_BURN && W.salved)
|
|
this_wound_desc = "salved [this_wound_desc]"
|
|
|
|
if(W.bleeding())
|
|
this_wound_desc = "bleeding [this_wound_desc]"
|
|
else if(W.bandaged)
|
|
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(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)
|
|
|
|
// Returns a list of the clothing (not glasses) that are covering this part
|
|
/obj/item/organ/external/proc/get_covering_clothing(var/target_covering) // target_covering checks for mouth/eye coverage
|
|
var/list/covering_clothing = list()
|
|
|
|
if(!target_covering)
|
|
target_covering = src.body_part_flags
|
|
|
|
if(owner)
|
|
var/list/protective_gear = list(owner.head, owner.wear_mask, owner.wear_suit, owner.w_uniform, owner.gloves, owner.shoes, owner.glasses)
|
|
for(var/obj/item/clothing/gear in protective_gear)
|
|
if(gear.body_cover_flags & target_covering)
|
|
covering_clothing |= gear
|
|
if(LAZYLEN(gear.accessories))
|
|
for(var/obj/item/clothing/accessory/bling in gear.accessories)
|
|
if(bling.body_cover_flags & src.body_part_flags)
|
|
covering_clothing |= bling
|
|
|
|
return covering_clothing
|
|
|
|
/obj/item/organ/external/proc/queue_syringe_infection()
|
|
if(!syringe_infection_queued)
|
|
syringe_infection_queued = 100
|
|
addtimer(CALLBACK(src, PROC_REF(do_syringe_infection)), rand(5, 10) MINUTES)
|
|
else
|
|
syringe_infection_queued = clamp(syringe_infection_queued + 10, 0, 300)
|
|
|
|
/obj/item/organ/external/proc/do_syringe_infection()
|
|
if(germ_level < syringe_infection_queued)
|
|
germ_level = syringe_infection_queued
|
|
syringe_infection_queued = null
|
|
|
|
/obj/item/organ/external/proc/has_genitals()
|
|
return !BP_IS_ROBOTIC(src) && species && species.sexybits_location == organ_tag
|
|
|
|
/obj/item/organ/external/proc/is_hidden_by_tail()
|
|
if(owner && owner.tail_style && owner.tail_style.hide_body_parts && (organ_tag in owner.tail_style.hide_body_parts))
|
|
return TRUE
|
|
|
|
/mob/living/carbon/human/proc/has_embedded_objects()
|
|
. = 0
|
|
for(var/obj/item/organ/external/L in organs)
|
|
for(var/obj/item/I in L.implants)
|
|
if(!istype(I,/obj/item/implant) && !istype(I,/obj/item/nif))
|
|
return TRUE
|
|
|
|
//* Hand Integration *//
|
|
|
|
// todo: some kind of API for querying what hands this organ provides
|
|
// this will require organs be composition instead of inheritance,
|
|
// as defining this on every left / right hand would be satanic.
|
|
|
|
//* Environmentals *//
|
|
|
|
// todo: limb specific
|
|
|
|
//* Physiology *//
|
|
|
|
/obj/item/organ/external/proc/rebuild_physiology()
|
|
physiology = new
|
|
for(var/datum/physiology_modifier/modifier as anything in physiology_modifiers)
|
|
if(!istype(modifier))
|
|
physiology_modifiers -= modifier
|
|
continue
|
|
physiology.apply(modifier)
|
|
|
|
/obj/item/organ/external/proc/init_local_physiology()
|
|
for(var/i in 1 to length(physiology_modifiers))
|
|
if(ispath(physiology_modifiers[i]))
|
|
physiology_modifiers[i] = cached_physiology_modifier(physiology_modifiers[i])
|
|
rebuild_physiology()
|
|
|
|
/obj/item/organ/external/proc/add_local_physiology_modifier(datum/physiology_modifier/modifier)
|
|
if(ispath(modifier))
|
|
modifier = cached_physiology_modifier(modifier)
|
|
ASSERT(!(modifier in physiology_modifiers))
|
|
LAZYADD(physiology_modifiers, modifier)
|
|
physiology.apply(modifier)
|
|
return TRUE
|
|
|
|
/obj/item/organ/external/proc/remove_local_physiology_modifier(datum/physiology_modifier/modifier)
|
|
if(ispath(modifier))
|
|
modifier = cached_physiology_modifier(modifier)
|
|
ASSERT(modifier in physiology_modifiers)
|
|
LAZYREMOVE(physiology_modifiers, modifier)
|
|
if(!physiology.revert(modifier))
|
|
// todo: optimize with reset().
|
|
rebuild_physiology()
|
|
return TRUE
|
|
|
|
/obj/item/organ/external/vv_get_dropdown()
|
|
. = ..()
|
|
VV_DROPDOWN_OPTION(null, "-----")
|
|
VV_DROPDOWN_OPTION(VV_HK_ADD_PHYSIOLOGY_MODIFIER, "Add Physiology Modifier")
|
|
VV_DROPDOWN_OPTION(VV_HK_REMOVE_PHYSIOLOGY_MODIFIER, "Remove Physiology Modifier")
|
|
|
|
/obj/item/organ/external/vv_do_topic(list/href_list)
|
|
. = ..()
|
|
if(href_list[VV_HK_ADD_PHYSIOLOGY_MODIFIER])
|
|
// todo: this should be able to be done globally via admin panel and then added to mobs
|
|
|
|
var/datum/physiology_modifier/modifier = ask_admin_for_a_physiology_modifier(usr)
|
|
|
|
if(isnull(modifier))
|
|
return
|
|
if(QDELETED(src))
|
|
return
|
|
|
|
log_admin("[key_name(usr)] --> organ [ref(src)] - added physiology modifier [json_encode(modifier.serialize())]")
|
|
add_local_physiology_modifier(modifier)
|
|
return TRUE
|
|
|
|
if(href_list[VV_HK_REMOVE_PHYSIOLOGY_MODIFIER])
|
|
var/list/assembled = list()
|
|
var/i = 0
|
|
for(var/datum/physiology_modifier/modifier as anything in physiology_modifiers)
|
|
assembled["[modifier.name] (#[++i])"] = modifier
|
|
var/picked = input(usr, "Which modifier to remove? Please do not do this unless you know what you are doing.", "Remove Physiology Modifier") as null|anything in assembled
|
|
var/datum/physiology_modifier/removing = assembled[picked]
|
|
if(!(removing in physiology_modifiers))
|
|
return TRUE
|
|
log_admin("[key_name(usr)] --> organ [ref(src)] - removed physiology modifier [json_encode(removing.serialize())]")
|
|
remove_local_physiology_modifier(removing)
|
|
return TRUE
|
|
|
|
// i'm not going to fucking support vv without automated backreferences and macros, holy shit.
|
|
// /obj/item/organ/external/proc/get_varedit_physiology_modifier()
|
|
// RETURN_TYPE(/datum/physiology_modifier)
|
|
// . = locate(/datum/physiology_modifier/varedit) in physiology_modifiers
|
|
// if(!isnull(.))
|
|
// return
|
|
// var/datum/physiology_modifier/varedit/new_holder = new
|
|
// add_local_physiology_modifier(new_holder)
|
|
// return new_holder
|