Improve, cleanup, and expand wound code (#20757)

This PR includes several changes to bring our wound code up to date with
baystation, nebula, and modern standards. Highlights include:
- The usage of integers between 0 and 100 for ratios (expressed as a
percentage), instead of 0.0 to 1.0. Small decimal values are prone to
floating point precision errors, but by moving to a percentage instead,
we can reduce the impact of this.
- The untangling of damage flags and injury flags, which were used
interchangably in the code. This could have caused issues should we
expand any of these.
- Larger embedded objects now halt bleeding in wounds, until they are
pulled out. In exchange however, embedded objects will halt healing in
wounds (it is difficult to close an open wound that currently has a rod
stuck in it)
- A lot of data, such as implanted objects and organs, are now stored in
amputated limbs.
This commit is contained in:
Cody Brittain
2025-12-19 15:22:29 -05:00
committed by GitHub
parent 034d0e4cb1
commit 96a2951608
23 changed files with 861 additions and 575 deletions
+2 -1
View File
@@ -3066,7 +3066,6 @@
#include "code\modules\organs\pain.dm"
#include "code\modules\organs\robolimbs.dm"
#include "code\modules\organs\tendons.dm"
#include "code\modules\organs\wound.dm"
#include "code\modules\organs\internal\_internal.dm"
#include "code\modules\organs\internal\appendix.dm"
#include "code\modules\organs\internal\brain.dm"
@@ -3137,6 +3136,8 @@
#include "code\modules\organs\subtypes\parasite\tumours.dm"
#include "code\modules\organs\subtypes\parasite\worms.dm"
#include "code\modules\organs\subtypes\parasite\zombie.dm"
#include "code\modules\organs\wounds\wound.dm"
#include "code\modules\organs\wounds\wound_types.dm"
#include "code\modules\overmap\_defines.dm"
#include "code\modules\overmap\overmap_object.dm"
#include "code\modules\overmap\overmap_shuttle.dm"
+6 -4
View File
@@ -8,10 +8,12 @@
#define DAMAGE_PAIN "pain"
#define DAMAGE_RADIATION "radiation"
#define CUT "cut"
#define BRUISE "bruise"
#define PIERCE "pierce"
//#define LASER "laser"
// Injury types for wounds
#define INJURY_TYPE_CUT "cut"
#define INJURY_TYPE_BRUISE "bruise"
#define INJURY_TYPE_BURN "burn"
#define INJURY_TYPE_PIERCE "pierce"
#define INJURY_TYPE_LASER "laser"
#define DAMAGE_FLAG_EDGE 1
#define DAMAGE_FLAG_SHARP 2
+27 -24
View File
@@ -45,43 +45,46 @@ BREATH ANALYZER
health_scan_mob(user, user, mode, sound_scan = sound_scan)
add_fingerprint(user)
/proc/get_wound_severity(var/damage_ratio, var/uppercase = FALSE) //Used for ratios.
/// Calculates severity based on the ratios defined external limbs.
/proc/get_wound_severity(damage_ratio, uppercase = FALSE)
var/degree = "none"
switch(damage_ratio)
if(0.001 to 0.1)
if (0 to 10)
degree = "minor"
if(0.1 to 0.2)
if (11 to 25)
degree = "moderate"
if(0.2 to 0.4)
if (26 to 50)
degree = "significant"
if(0.4 to 0.6)
if (51 to 75)
degree = "severe"
if(0.6 to 0.8)
degree = "critical"
if(0.8 to 1)
degree = "fatal"
if (76 to 99)
degree = "extreme"
if (99 to INFINITY)
degree = "irreparable"
if(uppercase)
degree = capitalize(degree)
return degree
/proc/get_severity(amount, var/uppercase = FALSE)
/proc/get_severity(amount, uppercase = FALSE)
var/output = "none"
if(!amount)
output = "none"
else if(amount > 100)
output = "fatal"
else if(amount > 75)
output = "critical"
else if(amount > 50)
output = "severe"
else if(amount > 25)
output = "significant"
else if(amount > 10)
output = "moderate"
else
output = "minor"
switch(amount)
if (0)
output = "none"
if (1 to 10)
output = "minor"
if (11 to 25)
output = "moderate"
if (26 to 50)
output = "significant"
if (51 to 75)
output = "severe"
if (76 to 99)
output = "extreme"
if (100 to INFINITY)
output = "irreparable"
if(uppercase)
output = capitalize(output)
+2 -2
View File
@@ -162,7 +162,7 @@ Contains:
user.visible_message(SPAN_NOTICE("\The [user] bandages \a [W.desc] on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You bandage \a [W.desc] on [target_mob]'s [affecting.name]."))
//H.add_side_effect("Itch")
else if (W.damage_type == BRUISE)
else if (W.damage_type == INJURY_TYPE_BRUISE)
user.visible_message(SPAN_NOTICE("\The [user] places a bruise patch over \a [W.desc] on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You place a bruise patch over \a [W.desc] on [target_mob]'s [affecting.name]."))
else
@@ -289,7 +289,7 @@ Contains:
user.visible_message(SPAN_NOTICE("\The [user] cleans \a [W.desc] on [target_mob]'s [affecting.name] and seals the edges with bioglue."), \
SPAN_NOTICE("You clean and seal \a [W.desc] on [target_mob]'s [affecting.name]."))
//H.add_side_effect("Itch")
else if (W.damage_type == BRUISE)
else if (W.damage_type == INJURY_TYPE_BRUISE)
user.visible_message(SPAN_NOTICE("\The [user] places a medical patch over \a [W.desc] on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You place a medical patch over \a [W.desc] on [target_mob]'s [affecting.name]."))
else
@@ -180,7 +180,7 @@
if(ishuman(imp_in) && part)
//No tearing off these parts since it's pretty much killing. Mangle them.
if(part.vital && !istype(part, /obj/item/organ/external/head)) //Head explodes
part.createwound(BRUISE, 70)
part.createwound(INJURY_TYPE_BRUISE, 70)
part.add_pain(50)
imp_in.visible_message(SPAN_WARNING("\The [imp_in]'s [part.name] bursts open with a horrible ripping noise!"),
SPAN_DANGER("Your [part.name] bursts open with a horrible ripping noise!"),
@@ -104,7 +104,7 @@
//Apply weapon damage
var/damage_flags = I.damage_flags()
apply_damage(effective_force, I.damtype, hit_zone, I, damage_flags, I.armor_penetration)
var/datum/wound/created_wound = apply_damage(effective_force, I.damtype, hit_zone, I, damage_flags, I.armor_penetration)
//Melee weapon embedded object code.
if (I && I.damtype == DAMAGE_BRUTE && !I.anchored && !is_robot_module(I) && I.canremove)
@@ -120,7 +120,7 @@
//Sharp objects will always embed if they do enough damage.
if((sharp && damage > (10*I.w_class)) || (damage > embed_threshold && prob(embed_chance)))
src.embed(I, hit_zone)
src.embed(I, hit_zone, supplied_wound = created_wound)
return 1
@@ -284,6 +284,15 @@
amount -= E.add_pain(amount)
BITSET(hud_updateflag, HEALTH_HUD)
/mob/living/carbon/human/proc/can_autoheal(dam_type)
if(!species || !dam_type) return FALSE
if (dam_type == DAMAGE_BRUTE)
return(getBruteLoss() < species.total_health * species.autoheal_brute_ratio)
else if (dam_type == DAMAGE_BURN)
return(getFireLoss() < species.total_health * species.autoheal_burn_ratio)
return FALSE
////////////////////////////////////////////
//Returns a list of damaged organs
@@ -362,14 +371,14 @@ In most cases it makes more sense to use apply_damage() instead! And make sure t
if(status_flags & GODMODE)
return //godmode
var/list/obj/item/organ/external/parts = get_damageable_organs()
var/update = 0
var/update
while(parts.len && (brute>0 || burn>0) )
var/obj/item/organ/external/picked = pick(parts)
var/brute_was = picked.brute_dam
var/burn_was = picked.burn_dam
update |= picked.take_damage(brute, burn, damage_flags, used_weapon)
update = picked.take_damage(brute, burn, damage_flags, used_weapon) ? TRUE : FALSE
brute -= (picked.brute_dam - brute_was)
burn -= (picked.burn_dam - burn_was)
@@ -486,18 +495,19 @@ This function restores all organs.
if(REAGENT_VOLUME(reagents, /singleton/reagent/adrenaline) < 15)
make_adrenaline(round(damage/10))
var/datum/wound/created_wound
switch(damagetype)
if(DAMAGE_BRUTE)
damageoverlaytemp = 20
if(damage > 0)
damage *= species.brute_mod
organ.take_damage(damage, 0, damage_flags, used_weapon)
created_wound = organ.take_damage(damage, 0, damage_flags, used_weapon)
UpdateDamageIcon()
if(DAMAGE_BURN)
damageoverlaytemp = 20
if(damage > 0)
damage *= species.burn_mod
organ.take_damage(0, damage, damage_flags, used_weapon)
created_wound = organ.take_damage(0, damage, damage_flags, used_weapon)
UpdateDamageIcon()
if(DAMAGE_PAIN)
organ.add_pain(damage)
@@ -507,7 +517,7 @@ This function restores all organs.
// Will set our damageoverlay icon to the next level, which will then be set back to the normal level the next mob.Life().
updatehealth()
BITSET(hud_updateflag, HEALTH_HUD)
return TRUE
return created_wound ? created_wound : TRUE
/mob/living/carbon/human/apply_radiation(var/rads)
if(species && rads > 0)
@@ -441,12 +441,12 @@ emp_act
Weaken(3)
visible_message(SPAN_WARNING("[src] get knocked over by [H]!"), SPAN_WARNING("You get knocked over by [H]!"))
/mob/living/carbon/human/embed(var/obj/O, var/def_zone=null)
/mob/living/carbon/human/embed(obj/O, def_zone=null, datum/wound/supplied_wound)
if(!def_zone) ..()
var/obj/item/organ/external/affecting = get_organ(def_zone)
if(affecting)
affecting.embed(O)
affecting.embed(O, supplied_wound = supplied_wound)
/mob/living/carbon/human/proc/bloody_hands(var/mob/living/source, var/amount = 2)
@@ -63,10 +63,9 @@
I.take_damage(rand(3,5))
//Moving makes open wounds get infected much faster
if (E.wounds.len)
for(var/datum/wound/W in E.wounds)
if (W.infection_check())
W.germ_level += 1
for(var/datum/wound/W in E.wounds)
if (W.infection_check())
W.germ_level += 1
/mob/living/carbon/human
var/next_stance_collapse = 0
@@ -122,6 +122,10 @@
// Combat vars.
/// Point at which the mob will enter crit.
var/total_health = 200
/// Ratio at which the mob will stop autohealing a wound. For brute damage.
var/autoheal_brute_ratio = 0.5
/// Ratio at which the mob will stop autohealing a wound. For burn damage.
var/autoheal_burn_ratio = 0.5
/// Possible unarmed attacks that the mob will use in combat,
var/list/unarmed_types = list(
/datum/unarmed_attack,
+1 -1
View File
@@ -322,7 +322,7 @@
src.anchored = 1
src.pinned += O
/mob/living/proc/embed(var/obj/O, var/def_zone=null)
/mob/living/proc/embed(obj/O, def_zone=null, datum/wound/supplied_wound)
O.forceMove(src)
src.embedded += O
add_verb(src, /mob/proc/yank_out_object)
+5 -3
View File
@@ -1089,13 +1089,13 @@
/mob/proc/embedded_needs_process()
return (embedded.len > 0)
/mob/proc/remove_implant(var/obj/item/implant, var/surgical_removal = FALSE)
/mob/proc/remove_implant(obj/item/implant, surgical_removal = FALSE)
if(!LAZYLEN(get_visible_implants(0))) //Yanking out last object - removing verb.
remove_verb(src, /mob/proc/yank_out_object)
for(var/obj/item/O in pinned)
if(O == implant)
pinned -= O
if(!pinned.len)
if(!length(pinned))
anchored = 0
implant.dropInto(loc)
implant.add_blood(src)
@@ -1114,6 +1114,8 @@
break
if(affected)
affected.implants -= implant
for(var/datum/wound/wound in affected.wounds)
LAZYREMOVE(wound.embedded_objects, implant)
if(!surgical_removal)
shock_stage += 20
apply_damage((implant.w_class * 7), DAMAGE_BRUTE, affected)
@@ -1162,7 +1164,7 @@
else
to_chat(U, SPAN_WARNING("You attempt to get a good grip on [selection] in [S]'s body."))
if(!do_after(U, 30))
if(!do_after(U, 3 SECONDS, S, DO_DEFAULT | DO_USER_UNIQUE_ACT, INCAPACITATION_DEFAULT & ~INCAPACITATION_FORCELYING)) //let people pinned to stuff yank it out, otherwise they're stuck... forever!!!
return
if(!selection || !S || !U)
return
+1 -1
View File
@@ -10,7 +10,7 @@
user.visible_message(SPAN_NOTICE("[user] starts inspecting [affecting]'s [E.name] carefully."))
if(!do_mob(user,H, 10))
to_chat(user, SPAN_NOTICE("You must stand still to inspect [E] for wounds."))
else if(E.wounds.len)
else if(LAZYLEN(E.wounds))
to_chat(user, SPAN_WARNING("You find [E.get_wounds_desc()]"))
else
to_chat(user, SPAN_NOTICE("You find no visible wounds."))
+14 -7
View File
@@ -422,7 +422,14 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
surge_damage = clamp(0, surge + surge_damage, MAXIMUM_SURGE_DAMAGE) //We want X seconds at most of hampered movement or what have you.
surge_time = world.time
/obj/item/organ/proc/removed(var/mob/living/carbon/human/target,var/mob/living/user)
/**
* Remove an organ
*
* drop_organ - if true, organ will be dropped at the loc of its former owner
* detach - if true, organ will be detached from parent. Keep false for organs
* removed together with parent, as with an amputation.
*/
/obj/item/organ/proc/removed(mob/living/carbon/human/target, mob/living/user, drop_organ = TRUE, detach = TRUE)
if(!istype(owner))
return
@@ -433,13 +440,13 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
owner.internal_organs_by_name -= null
owner.internal_organs -= src
var/obj/item/organ/external/affected = owner.get_organ(parent_organ)
if(affected) affected.internal_organs -= src
if(detach)
var/obj/item/organ/external/affected = owner.get_organ(parent_organ)
if(affected)
affected.internal_organs -= src
var/turf/T = get_turf(owner)
// to avoid brains and things like that getting put into nullspace and thus entering spatial grids
if(!isnull(T))
loc = T
if(drop_organ)
dropInto(owner.loc)
START_PROCESSING(SSprocessing, src)
rejecting = null
if (!reagents)
+159 -100
View File
@@ -84,7 +84,7 @@
var/hair_color
/// A `/list` of wounds
var/list/datum/wound/wounds = list()
var/list/wounds
/// A list of implants present in this organ
var/list/implants = list()
@@ -223,6 +223,9 @@
limb_flags &= ~ORGAN_HAS_TENDON
/obj/item/organ/external/Destroy()
. = ..()
if(parent?.children)
parent.children -= src
@@ -245,9 +248,11 @@
cached_markings = null
mob_icon = null
for(var/datum/wound/wound in wounds)
qdel(wound)
QDEL_LIST(children)
QDEL_LIST(internal_organs)
QDEL_LIST(wounds)
QDEL_LIST(implants)
infect_target_internal = null
@@ -259,13 +264,12 @@
QDEL_NULL(tendon)
. = ..()
/obj/item/organ/external/proc/invalidate_marking_cache()
cached_markings = null
/obj/item/organ/external/attack_self(var/mob/user)
if(!contents.len)
if(!length(contents))
return ..()
var/list/removable_objects = list()
for(var/obj/item/organ/external/E in (contents + src))
@@ -275,7 +279,7 @@
if(istype(I,/obj/item/organ))
continue
removable_objects |= I
if(removable_objects.len)
if(length(removable_objects))
var/obj/item/I = pick(removable_objects)
I.forceMove(get_turf(user)) //just in case something was embedded that is not an item
if(istype(I))
@@ -307,9 +311,18 @@
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))
var/list/organs = get_contents_recursive()
if(length(organs))
var/obj/item/removing = pick(organs)
var/obj/item/organ/external/current_child = removing.loc
current_child.implants.Remove(removing)
current_child.internal_organs.Remove(removing)
status |= ORGAN_CUT_AWAY
removing.forceMove(get_turf(user))
if(!(user.l_hand && user.r_hand))
user.put_in_hands(removing)
user.visible_message(SPAN_DANGER("<b>[user]</b> extracts [removing] from [src] with [attacking_item]!"))
@@ -318,6 +331,20 @@
return
..()
/**
* Get a list of contents of this organ and all the child organs
*/
/obj/item/organ/external/proc/get_contents_recursive()
var/list/all_items = list()
all_items.Add(implants)
all_items.Add(internal_organs)
for(var/obj/item/organ/external/child in children)
all_items.Add(child.get_contents_recursive())
return all_items
/obj/item/organ/external/proc/dislocate(var/primary)
if(dislocated == -1)
return
@@ -358,9 +385,22 @@
owner.organs |= src
if(!species)
species = owner.species
for(var/obj/item/organ/organ in src)
for(var/obj/item/organ/organ in internal_organs)
organ.replaced(owner,src)
for(var/obj/implant in implants)
implant.forceMove(owner)
if(istype(implant, /obj/item/implant))
var/obj/item/implant/imp_device = implant
// we can't use implanted() here since it's often interactive
imp_device.imp_in = owner
imp_device.implanted = 1
for(var/obj/item/organ/external/organ in children)
organ.replaced(owner)
if(!parent && parent_organ)
parent = owner.organs_by_name[src.parent_organ]
if(parent)
@@ -369,7 +409,6 @@
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()
@@ -386,12 +425,11 @@
return (BP_IS_ROBOTIC(src) || brute_dam + burn_dam + additional_damage < max_damage * 4)
/obj/item/organ/external/take_damage(brute, burn, damage_flags, used_weapon = null, list/forbidden_limbs = list(), var/silent)
brute = round(brute * brute_mod, 0.1)
burn = round(burn * burn_mod, 0.1)
if((brute <= 0) && (burn <= 0))
return 0
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)
@@ -434,21 +472,22 @@
brute /= 2
burn /= 2
var/datum/wound/created_wound
var/can_cut = !BP_IS_ROBOTIC(src) && (sharp || prob(brute))
if(brute)
var/to_create = BRUISE
var/to_create = INJURY_TYPE_BRUISE
if(can_cut)
to_create = CUT
to_create = INJURY_TYPE_CUT
//need to check sharp again here so that blunt damage that was strong enough to break skin doesn't give puncture wounds
if(sharp && !edge)
to_create = PIERCE
createwound(to_create, brute)
to_create = INJURY_TYPE_PIERCE
created_wound = createwound(to_create, brute)
if(burn)
if(laser)
createwound(LASER, burn)
created_wound = createwound(INJURY_TYPE_LASER, burn)
else
createwound(DAMAGE_BURN, burn)
created_wound = createwound(INJURY_TYPE_BURN, burn)
add_pain(0.6 * burn + 0.4 * brute)
@@ -460,8 +499,9 @@
update_damages()
if(owner)
owner.updatehealth() //droplimb will call updatehealth() again if it does end up being called
update_icon()
return update_icon()
return created_wound
/obj/item/organ/external/proc/damage_internal_organs(brute, burn, damage_flags)
if(!length(internal_organs))
@@ -553,7 +593,7 @@
break
// heal brute damage
if(W.damage_type == DAMAGE_BURN)
if(W.damage_type == INJURY_TYPE_BURN)
burn = W.heal_damage(burn)
else
brute = W.heal_damage(brute)
@@ -581,7 +621,7 @@ This function completely restores a damaged organ to perfect condition.
brute_dam = 0
burn_dam = 0
germ_level = 0
wounds.Cut()
QDEL_LIST(wounds)
number_wounds = 0
// handle internal organs
@@ -600,7 +640,7 @@ This function completely restores a damaged organ to perfect condition.
owner.updatehealth()
/obj/item/organ/external/proc/createwound(var/type = CUT, var/damage)
/obj/item/organ/external/proc/createwound(type = INJURY_TYPE_CUT, damage)
if(damage <= 0 || !owner)
return
@@ -608,41 +648,41 @@ This function completely restores a damaged organ to perfect condition.
//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))
var/is_burn_type_damage = (type in list(INJURY_TYPE_BURN, INJURY_TYPE_LASER))
var/is_brute_type_damage = (type in list(INJURY_TYPE_CUT, INJURY_TYPE_PIERCE, INJURY_TYPE_BRUISE))
if(damage > (min_broken_damage / 2) && local_damage > min_broken_damage && !(status & ORGAN_ROBOT))
if(!is_burn_type_damage)
if(prob(damage) && sever_artery())
owner.custom_pain("You feel something rip in your [name]!", 25)
if(damage > (min_broken_damage / 2) && local_damage > min_broken_damage && !(status & ORGAN_ROBOT) && is_brute_type_damage)
if(prob(damage) && sever_artery())
owner.custom_pain("You feel something rip in your [name]!", 25)
if(istype(tendon) && type == CUT)
if(istype(tendon))
var/new_brute = damage
if(min_broken_damage - brute_dam > 0)
if(min_broken_damage - damage > 0)
// Only pass on damage that goes over the broken threshold
new_brute = brute_dam + damage - min_broken_damage
new_brute = damage - min_broken_damage
tendon.damage(new_brute)
//Burn damage can cause fluid loss due to blistering and cook-off
if(is_burn_type_damage && (damage > 5 || damage + burn_dam >= 15) && !BP_IS_ROBOTIC(src))
var/fluid_loss_severity
switch(type)
if(DAMAGE_BURN)
if(INJURY_TYPE_BURN)
fluid_loss_severity = FLUIDLOSS_WIDE_BURN
if(LASER)
if(INJURY_TYPE_LASER)
fluid_loss_severity = FLUIDLOSS_CONC_BURN
var/fluid_loss = (damage/(owner.maxHealth - GLOB.config.health_threshold_dead)) * DEFAULT_BLOOD_AMOUNT * fluid_loss_severity
owner.remove_blood_simple(fluid_loss)
// first check whether we can widen an existing wound
if(wounds.len > 0 && prob(max(50+(number_wounds-1)*10,90)))
if((type == CUT || type == BRUISE) && damage >= 5)
if(LAZYLEN(wounds) && prob(max(50+(number_wounds-1)*10,90)))
if((type == INJURY_TYPE_CUT || type == INJURY_TYPE_BRUISE) && damage >= 5)
//we need to make sure that the wound we are going to worsen is compatible with the type of damage...
var/list/compatible_wounds = list()
for (var/datum/wound/W in wounds)
if (W.can_worsen(type, damage))
compatible_wounds += W
if(compatible_wounds.len)
if(length(compatible_wounds))
var/datum/wound/W = pick(compatible_wounds)
W.open_wound(damage)
@@ -661,13 +701,13 @@ This function completely restores a damaged organ to perfect condition.
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
return W
//Creating wound
var/wound_type = get_wound_type(type, damage)
if(wound_type)
var/datum/wound/W = new wound_type(damage)
var/datum/wound/W = new wound_type(damage, src)
//Check whether we can add the wound to an existing wound
for(var/datum/wound/other in wounds)
@@ -675,13 +715,13 @@ This function completely restores a damaged organ to perfect condition.
other.merge_wound(W)
W = null // to signify that the wound was added
break
if(W)
wounds += W
LAZYADD(wounds, W)
if(bandage_level)
owner.visible_message(SPAN_WARNING("The bandages on [owner.name]'s [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!"), SPAN_WARNING("The bandages on your [name] gets [is_burn_type_damage ? "burnt" : "ripped"] off!"))
bandage_level = BANDAGE_LEVEL_NONE
owner.update_bandages()
return W
/****************************************************
PROCESSING & UPDATING
@@ -926,8 +966,8 @@ Note that amputating the affected organ does in fact remove the infection from t
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
if(W.damage <= 0 && W.created + (10 MINUTES) <= world.time)
qdel(W)
continue
// let the GC handle the deletion of the wound
@@ -938,7 +978,7 @@ Note that amputating the affected organ does in fact remove the infection from t
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)
if (W.can_autoheal() && W.wound_damage() && brute_ratio < 50 && burn_ratio < 50)
heal_amt += 0.5
//we only update wounds once in [wound_update_accuracy] ticks so have to emulate realtime
@@ -946,14 +986,19 @@ Note that amputating the affected organ does in fact remove the infection from t
//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)
heal_amt = heal_amt / (LAZYLEN(wounds) + 1)
// making it look prettier on scanners
heal_amt = round(heal_amt,0.1)
W.heal_damage(heal_amt)
var/dam_type = DAMAGE_BRUTE
if (W.damage_type == INJURY_TYPE_BURN)
dam_type = DAMAGE_BURN
if(owner.can_autoheal(dam_type) && (heal_amt > 0))
W.heal_damage(heal_amt)
// Salving also helps against infection
if(W.germ_level > 0 && W.salved && prob(2))
W.disinfected = 1
W.disinfected = TRUE
W.germ_level = 0
// sync the organ's damage with its wounds
@@ -981,11 +1026,11 @@ Note that amputating the affected organ does in fact remove the infection from t
//update damage counts
for(var/datum/wound/W in wounds)
if(W.damage_type == DAMAGE_BURN)
if(W.damage_type == INJURY_TYPE_BURN)
burn_dam += W.damage
else
brute_dam += W.damage
if(W.damage_type == CUT)
if(W.damage_type == INJURY_TYPE_CUT)
cut_dam += W.damage
if(!(status & ORGAN_ROBOT) && W.bleeding() && (H && !(H.species.flags & NO_BLOOD)))
@@ -1005,9 +1050,9 @@ Note that amputating the affected organ does in fact remove the infection from t
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)
var/limb_loss_threshold = max_damage * 2
brute_ratio = Percent(brute_dam, limb_loss_threshold)
burn_ratio = Percent(burn_dam, limb_loss_threshold)
// new damage icon system
// returns just the brute/burn damage code
@@ -1046,7 +1091,7 @@ Note that amputating the affected organ does in fact remove the infection from t
dir = 2
//Handles dismemberment
/obj/item/organ/external/proc/droplimb(var/clean, var/disintegrate = DROPLIMB_EDGE, var/ignore_children = null)
/obj/item/organ/external/proc/droplimb(clean, disintegrate = DROPLIMB_EDGE, ignore_children = null)
if(!(limb_flags & ORGAN_CAN_AMPUTATE) || !owner)
return
@@ -1073,7 +1118,7 @@ Note that amputating the affected organ does in fact remove the infection from t
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
var/obj/item/organ/external/original_parent = parent
if(!clean)
victim.shock_stage += min_broken_damage
@@ -1084,18 +1129,18 @@ Note that amputating the affected organ does in fact remove the infection from t
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)
if(original_parent)
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()
var/obj/item/organ/external/damaged_organ = original_parent
if(!clean)
var/obj/item/organ/external/stump/stump = new (victim, 0, src)
stump.add_pain(max_damage)
damaged_organ = stump
if(disintegrate != DROPLIMB_BURN)
stump.sever_artery()
W.parent_organ = damaged_organ
LAZYADD(damaged_organ.wounds, W)
damaged_organ.update_damages()
post_droplimb(victim)
SEND_SIGNAL(victim, COMSIG_LIMB_LOSS)
@@ -1107,6 +1152,7 @@ Note that amputating the affected organ does in fact remove the infection from t
var/matrix/M = matrix()
M.Turn(rand(180))
src.transform = M
forceMove(get_turf(src))
if(!clean)
//Throw limb around.
if(src && isturf(loc))
@@ -1131,18 +1177,9 @@ Note that amputating the affected organ does in fact remove the infection from t
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()
victim.blood_squirt(2, loc, rand(2,5))
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)
I.dropInto(Tloc)
INVOKE_ASYNC(I, TYPE_PROC_REF(/atom/movable, throw_at), get_edge_target_turf(src, pick(GLOB.alldirs)), rand(1,3), 4)
qdel(src)
@@ -1196,21 +1233,21 @@ Note that amputating the affected organ does in fact remove the infection from t
status &= ~ORGAN_BLEEDING
for(var/datum/wound/W in wounds)
rval |= !W.bandaged
W.bandaged = 1
W.bandage()
return rval
/obj/item/organ/external/proc/salve()
var/rval = 0
for(var/datum/wound/W in wounds)
rval |= !W.salved
W.salved = 1
W.salve()
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.disinfect()
W.germ_level = 0
return rval
@@ -1353,14 +1390,14 @@ Note that amputating the affected organ does in fact remove the infection from t
return FALSE
if(get_pain() > pain_disability_threshold)
return FALSE
if(brute_ratio > 1)
if(brute_ratio >= 100)
return FALSE
if(burn_ratio > 1)
if(burn_ratio >= 100)
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)))
if(BP_IS_ROBOTIC(src) && (brute_ratio + burn_ratio) >= 10 && prob(brute_dam + burn_dam) || (surge_damage > (MAXIMUM_SURGE_DAMAGE * 0.25)))
return TRUE
if(robotize_type)
var/datum/robolimb/R = GLOB.all_robolimbs[robotize_type]
@@ -1369,7 +1406,7 @@ Note that amputating the affected organ does in fact remove the infection from t
else
return FALSE
/obj/item/organ/external/proc/embed(var/obj/item/W, var/silent = 0, var/supplied_message)
/obj/item/organ/external/proc/embed(obj/item/W, silent = FALSE, supplied_message, datum/wound/supplied_wound)
if(!owner || loc != owner)
return
if(!W.canremove || is_robot_module(W)) //Modules and augments cannot embed
@@ -1383,6 +1420,16 @@ Note that amputating the affected organ does in fact remove the infection from t
owner.visible_message(SPAN_DANGER("\The [W] sticks in [owner]'s wound!"),
SPAN_DANGER("\The [W] sticks in your wound!"))
if(supplied_wound)
for(var/datum/wound/wound in wounds)
if ((wound.damage_type == INJURY_TYPE_CUT || wound.damage_type == INJURY_TYPE_PIERCE) && wound.damage >= W.w_class * 5)
supplied_wound = wound
break
if(!supplied_wound || (W in supplied_wound.embedded_objects)) // Just in case.
return
LAZYADD(supplied_wound.embedded_objects, W)
implants += W
owner.embedded_flag = 1
add_verb(owner, /mob/proc/yank_out_object)
@@ -1390,14 +1437,13 @@ Note that amputating the affected organ does in fact remove the infection from t
if(ismob(W.loc))
var/mob/living/H = W.loc
H.drop_from_inventory(W,owner)
else
W.forceMove(owner)
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/is_robotic = BP_IS_ROBOTIC(src)
var/mob/living/carbon/human/victim = owner
..(null, user)
@@ -1407,24 +1453,30 @@ Note that amputating the affected organ does in fact remove the infection from t
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
if(istype(I) && I.w_class < WEIGHT_CLASS_NORMAL)
implant.forceMove(src)
implants.Cut()
// let actual implants still inside know they're no longer implanted
if(istype(I, /obj/item/implant))
var/obj/item/implant/imp_device = I
imp_device.removed()
else
implants.Remove(implant)
implant.forceMove(get_turf(src))
// Attached organs also fly off.
if(!ignore_children)
for(var/obj/item/organ/external/O in children)
O.removed()
if(O)
if(!QDELETED(O))
O.forceMove(src)
for(var/obj/item/I in O.contents)
I.forceMove(src)
// if we didn't lose the organ we still want it as a child
children += O
O.parent = src
// Grab all the internal giblets too.
for(var/obj/item/organ/organ in internal_organs)
organ.removed()
organ.removed(user, FALSE, FALSE) // Organ stays inside and connected
organ.forceMove(src)
// Remove parent references
@@ -1434,6 +1486,7 @@ Note that amputating the affected organ does in fact remove the infection from t
release_restraints(victim)
victim.organs -= src
victim.organs_by_name[limb_name] = null // Remove from owner's vars.
victim.organs_by_name -= organ_tag
//Robotic limbs explode if sabotaged.
if(is_robotic && sabotaged)
@@ -1497,14 +1550,20 @@ Note that amputating the affected organ does in fact remove the infection from t
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)
if(W.damage_type == DAMAGE_BURN && W.salved)
this_wound_desc = "salved [this_wound_desc]"
if(W.bleeding())
if(W.wound_damage() > W.bleed_threshold)
this_wound_desc = "<b>bleeding</b> [this_wound_desc]"
else
this_wound_desc = "bleeding [this_wound_desc]"
else if(W.bandaged == TRUE)
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(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
@@ -1520,7 +1579,7 @@ Note that amputating the affected organ does in fact remove the infection from t
var/list/bits = list()
for(var/obj/item/organ/internal/organ in internal_organs)
bits += organ.get_visible_state()
if(bits.len)
if(length(bits))
wound_descriptors["[english_list(bits)] visible in the wounds"] = 1
if(wound_descriptors.len)
-1
View File
@@ -11,7 +11,6 @@
amputation_point = limb.amputation_point
joint = limb.joint
parent_organ = limb.parent_organ
wounds = limb.wounds
. = ..(mapload, internal)
if(istype(limb))
max_damage = limb.max_damage
@@ -108,7 +108,7 @@
var/list/brute_wounds = list()
for(var/wound in O.wounds)
var/datum/wound/W = wound
if(W.damage_type in list(CUT, BRUISE, PIERCE))
if(W.damage_type in list(INJURY_TYPE_CUT, INJURY_TYPE_BRUISE, INJURY_TYPE_PIERCE))
brute_wounds += W
for(var/wound in brute_wounds)
var/datum/wound/W = wound
-410
View File
@@ -1,410 +0,0 @@
/****************************************************
WOUNDS
****************************************************/
/datum/wound
/// number representing the current stage
var/current_stage = 0
/// description of the wound
var/desc = "wound" //default in case something borks
/// amount of damage this wound causes
var/damage = 0
/// how many times we've tried to lower the bleed timer, used to determine whether hemophilia should let bleed timer lower
var/bleed_timer_increment = 0
/// ticks of bleeding left.
var/bleed_timer = 0
/// Above this amount wounds you will need to treat the wound to stop bleeding, regardless of bleed_timer
var/bleed_threshold = 30
/// amount of damage the current wound type requires(less means we need to apply the next healing stage)
var/min_damage = 0
/// is the wound bandaged?
var/bandaged = 0
/// Similar to bandaged, but works differently
var/clamped = 0
/// is the wound salved?
var/salved = 0
/// is the wound disinfected?
var/disinfected = 0
var/created = 0
/// number of wounds of this type
var/amount = 1
/// amount of germs in the wound
var/germ_level = 0
/* These are defined by the wound type and should not be changed */
/// stages such as "cut", "deep cut", etc.
var/list/stages
/// maximum stage at which bleeding should still happen. Beyond this stage bleeding is prevented.
var/max_bleeding_stage = 0
/// one of CUT, BRUISE, PIERCE, BURN
var/damage_type = CUT
/// whether this wound needs a bandage/salve to heal at all
/// the maximum amount of damage that this wound can have and still autoheal
var/autoheal_cutoff = 15
// helper lists
var/tmp/list/desc_list = list()
var/tmp/list/damage_list = list()
/datum/wound/New(var/damage)
created = world.time
// reading from a list("stage" = damage) is pretty difficult, so build two separate
// lists from them instead
for(var/V in stages)
desc_list += V
damage_list += stages[V]
src.damage = damage
// initialize with the appropriate stage
src.init_stage(damage)
bleed_timer += damage
// returns 1 if there's a next stage, 0 otherwise
/datum/wound/proc/init_stage(var/initial_damage)
current_stage = stages.len
while(src.current_stage > 1 && src.damage_list[current_stage-1] <= initial_damage / src.amount)
src.current_stage--
src.min_damage = damage_list[current_stage]
src.desc = desc_list[current_stage]
// the amount of damage per wound
/datum/wound/proc/wound_damage()
return src.damage / src.amount
/datum/wound/proc/can_autoheal()
if(src.wound_damage() <= autoheal_cutoff)
return 1
return is_treated()
// checks whether the wound has been appropriately treated
/datum/wound/proc/is_treated()
switch(damage_type)
if(BRUISE, CUT, PIERCE)
return bandaged
if(DAMAGE_BURN)
return salved
// Checks whether other other can be merged into src.
/datum/wound/proc/can_merge(var/datum/wound/other)
if (other.type != src.type) return 0
if (other.current_stage != src.current_stage) return 0
if (other.damage_type != src.damage_type) return 0
if (!(other.can_autoheal()) != !(src.can_autoheal())) return 0
if (!(other.bandaged) != !(src.bandaged)) return 0
if (!(other.clamped) != !(src.clamped)) return 0
if (!(other.salved) != !(src.salved)) return 0
if (!(other.disinfected) != !(src.disinfected)) return 0
//if (other.germ_level != src.germ_level) return 0
return 1
/datum/wound/proc/merge_wound(var/datum/wound/other)
src.damage += other.damage
src.amount += other.amount
src.bleed_timer += other.bleed_timer
src.bleed_timer_increment += other.bleed_timer_increment
src.germ_level = (src.germ_level + other.germ_level)/2
src.created = max(src.created, other.created) //take the newer created time
// checks if wound is considered open for external infections
// untreated cuts (and bleeding bruises) and burns are possibly infectable, chance higher if wound is bigger
/datum/wound/proc/infection_check()
if (damage < 10) //small cuts, tiny bruises, and moderate burns shouldn't be infectable.
return 0
if (is_treated() && damage < 25) //anything less than a flesh wound (or equivalent) isn't infectable if treated properly
return 0
if (disinfected)
germ_level = 0 //reset this, just in case
return 0
if (damage_type == BRUISE && !bleeding()) //bruises only infectable if bleeding
return 0
var/dam_coef = round(damage/10)
switch (damage_type)
if (BRUISE)
return prob(dam_coef*5)
if (DAMAGE_BURN)
return prob(dam_coef*10)
if (CUT)
return prob(dam_coef*20)
return 0
/datum/wound/proc/bandage(var/examine_desc)
if(!examine_desc)
bandaged = 1
else bandaged = examine_desc
/datum/wound/proc/salve()
salved = 1
/datum/wound/proc/disinfect()
disinfected = 1
// heal the given amount of damage, and if the given amount of damage was more
// than what needed to be healed, return how much heal was left
// set @heals_internal to also heal internal organ damage
/datum/wound/proc/heal_damage(amount, heals_internal = 0)
var/healed_damage = min(src.damage, amount)
amount -= healed_damage
src.damage -= healed_damage
while(src.wound_damage() < damage_list[current_stage] && current_stage < src.desc_list.len)
current_stage++
desc = desc_list[current_stage]
src.min_damage = damage_list[current_stage]
// return amount of healing still leftover, can be used for other wounds
return amount
// opens the wound again
/datum/wound/proc/open_wound(damage)
src.damage += damage
bleed_timer += damage
while(src.current_stage > 1 && src.damage_list[current_stage-1] <= src.damage / src.amount)
src.current_stage--
src.desc = desc_list[current_stage]
src.min_damage = damage_list[current_stage]
src.bandaged = FALSE
// returns whether this wound can absorb the given amount of damage.
// this will prevent large amounts of damage being trapped in less severe wound types
/datum/wound/proc/can_worsen(damage_type, damage)
if (src.damage_type != damage_type)
return 0 //incompatible damage types
if (src.amount > 1)
return 0
//with 1.5*, a shallow cut will be able to carry at most 30 damage,
//37.5 for a deep cut
//52.5 for a flesh wound, etc.
var/max_wound_damage = 1.5*src.damage_list[1]
if (src.damage + damage > max_wound_damage)
return 0
return 1
/datum/wound/proc/bleeding()
if (current_stage > max_bleeding_stage)
return 0
if (bandaged||clamped)
return 0
if (bleed_timer <= 0 && wound_damage() <= bleed_threshold)
return 0 //Bleed timer has run out. Once a wound is big enough though, you'll need a bandage to stop it
return 1
/// Called in organ_external.dm update_damages, this will update the limb's status to bleeding, and lowers the bleed_timer if applicable
/datum/wound/proc/handle_bleeding(var/mob/victim, var/obj/item/organ/external/limb)
limb.status |= ORGAN_BLEEDING
var/can_lower_bleed = TRUE
if(HAS_TRAIT(victim, TRAIT_DISABILITY_HEMOPHILIA_MAJOR)) // major will NEVER lower
can_lower_bleed = FALSE
else if(HAS_TRAIT(victim, TRAIT_DISABILITY_HEMOPHILIA) && bleed_timer_increment % 2 != 0) // basic will lower half as fast
can_lower_bleed = FALSE
if(can_lower_bleed)
bleed_timer--
bleed_timer_increment++
/** WOUND DEFINITIONS **/
//Note that the MINIMUM damage before a wound can be applied should correspond to
//the damage amount for the stage with the same name as the wound.
//e.g. /datum/wound/cut/deep should only be applied for 15 damage and up,
//because in it's stages list, "deep cut" = 15.
/proc/get_wound_type(var/type = CUT, var/damage)
switch(type)
if(CUT)
switch(damage)
if(70 to INFINITY)
return /datum/wound/cut/massive
if(60 to 70)
return /datum/wound/cut/gaping_big
if(50 to 60)
return /datum/wound/cut/gaping
if(25 to 50)
return /datum/wound/cut/flesh
if(15 to 25)
return /datum/wound/cut/deep
if(0 to 15)
return /datum/wound/cut/small
if(PIERCE)
switch(damage)
if(60 to INFINITY)
return /datum/wound/puncture/massive
if(50 to 60)
return /datum/wound/puncture/gaping_big
if(30 to 50)
return /datum/wound/puncture/gaping
if(15 to 30)
return /datum/wound/puncture/flesh
if(0 to 15)
return /datum/wound/puncture/small
if(BRUISE)
return /datum/wound/bruise
if(DAMAGE_BURN, LASER)
switch(damage)
if(50 to INFINITY)
return /datum/wound/burn/carbonised
if(40 to 50)
return /datum/wound/burn/deep
if(30 to 40)
return /datum/wound/burn/severe
if(15 to 30)
return /datum/wound/burn/large
if(0 to 15)
return /datum/wound/burn/moderate
return null //no wound
/** CUTS **/
/datum/wound/cut
bleed_threshold = 5
damage_type = CUT
/datum/wound/cut/small
// link wound descriptions to amounts of damage
// Minor cuts have max_bleeding_stage set to the stage that bears the wound type's name.
// The major cut types have the max_bleeding_stage set to the clot stage (which is accordingly given the "blood soaked" descriptor).
max_bleeding_stage = 3
stages = list("ugly ripped cut" = 20, "ripped cut" = 10, "cut" = 5, "healing cut" = 2, "small scab" = 0)
/datum/wound/cut/deep
max_bleeding_stage = 3
stages = list("ugly deep ripped cut" = 25, "deep ripped cut" = 20, "deep cut" = 15, "clotted cut" = 8, "scab" = 2, "fresh skin" = 0)
/datum/wound/cut/flesh
max_bleeding_stage = 4
stages = list("ugly ripped flesh wound" = 35, "ugly flesh wound" = 30, "flesh wound" = 25, "blood soaked clot" = 15, "large scab" = 5, "fresh skin" = 0)
/datum/wound/cut/gaping
max_bleeding_stage = 3
stages = list("gaping wound" = 50, "large blood soaked clot" = 25, "blood soaked clot" = 15, "small angry scar" = 5, "small straight scar" = 0)
/datum/wound/cut/gaping_big
max_bleeding_stage = 3
stages = list("big gaping wound" = 60, "healing gaping wound" = 40, "large blood soaked clot" = 25, "large angry scar" = 10, "large straight scar" = 0)
/datum/wound/cut/massive
max_bleeding_stage = 3
stages = list("massive wound" = 70, "massive healing wound" = 50, "massive blood soaked clot" = 25, "massive angry scar" = 10, "massive jagged scar" = 0)
/** PUNCTURES **/
/datum/wound/puncture
bleed_threshold = 10
damage_type = PIERCE
/datum/wound/puncture/can_worsen(damage_type, damage)
return 0
/datum/wound/puncture/small
max_bleeding_stage = 2
stages = list("puncture" = 5, "healing puncture" = 2, "small scab" = 0)
/datum/wound/puncture/flesh
max_bleeding_stage = 2
stages = list("puncture wound" = 15, "blood soaked clot" = 5, "large scab" = 2, "small round scar" = 0)
/datum/wound/puncture/gaping
max_bleeding_stage = 3
stages = list("gaping hole" = 30, "large blood soaked clot" = 15, "blood soaked clot" = 10, "small angry scar" = 5, "small round scar" = 0)
/datum/wound/puncture/gaping_big
max_bleeding_stage = 3
stages = list("big gaping hole" = 50, "healing gaping hole" = 20, "large blood soaked clot" = 15, "large angry scar" = 10, "large round scar" = 0)
/datum/wound/puncture/massive
max_bleeding_stage = 3
stages = list("massive wound" = 60, "massive healing wound" = 30, "massive blood soaked clot" = 25, "massive angry scar" = 10, "massive jagged scar" = 0)
/** BRUISES **/
/datum/wound/bruise
stages = list("monumental bruise" = 80, "huge bruise" = 50, "large bruise" = 30,
"moderate bruise" = 20, "small bruise" = 10, "tiny bruise" = 5)
bleed_threshold = 20
max_bleeding_stage = 3 //only large bruise and above can bleed.
autoheal_cutoff = 30
damage_type = BRUISE
/** BURNS **/
/datum/wound/burn
damage_type = DAMAGE_BURN
max_bleeding_stage = 0
/datum/wound/burn/bleeding()
return 0
/datum/wound/burn/moderate
stages = list("ripped burn" = 10, "moderate burn" = 5, "healing moderate burn" = 2, "fresh skin" = 0)
/datum/wound/burn/large
stages = list("ripped large burn" = 20, "large burn" = 15, "healing large burn" = 5, "fresh skin" = 0)
/datum/wound/burn/severe
stages = list("ripped severe burn" = 35, "severe burn" = 30, "healing severe burn" = 10, "burn scar" = 0)
/datum/wound/burn/deep
stages = list("ripped deep burn" = 45, "deep burn" = 40, "healing deep burn" = 15, "large burn scar" = 0)
/datum/wound/burn/carbonised
stages = list("carbonised area" = 50, "healing carbonised area" = 20, "massive burn scar" = 0)
/** EXTERNAL ORGAN LOSS **/
/datum/wound/lost_limb
/datum/wound/lost_limb/New(var/obj/item/organ/external/lost_limb, var/losstype, var/clean)
var/damage_amt = lost_limb.max_damage
if(clean) damage_amt /= 2
switch(losstype)
if(DROPLIMB_EDGE, DROPLIMB_BLUNT)
damage_type = CUT
max_bleeding_stage = 3 //clotted stump and above can bleed.
stages = list(
"ripped stump" = damage_amt*1.3,
"bloody stump" = damage_amt,
"clotted stump" = damage_amt*0.5,
"scarred stump" = 0
)
if(DROPLIMB_BURN)
damage_type = DAMAGE_BURN
stages = list(
"ripped charred stump" = damage_amt*1.3,
"charred stump" = damage_amt,
"scarred stump" = damage_amt*0.5,
"scarred stump" = 0
)
..(damage_amt)
/datum/wound/lost_limb/can_merge(var/datum/wound/other)
return 0 //cannot be merged
+254
View File
@@ -0,0 +1,254 @@
/****************************************************
WOUNDS
****************************************************/
/datum/wound
/// number representing the current stage
var/current_stage = 0
/// description of the wound
var/desc = "wound" //default in case something borks
/// amount of damage this wound causes
var/damage = 0
/// how many times we've tried to lower the bleed timer, used to determine whether hemophilia should let bleed timer lower
var/bleed_timer_increment = 0
/// ticks of bleeding left.
var/bleed_timer = 0
/// Above this amount wounds you will need to treat the wound to stop bleeding, regardless of bleed_timer
var/bleed_threshold = 30
/// amount of damage the current wound type requires(less means we need to apply the next healing stage)
var/min_damage = 0
/// is the wound bandaged?
var/bandaged = FALSE
/// Similar to bandaged, but works differently
var/clamped = FALSE
/// is the wound salved?
var/salved = FALSE
/// is the wound disinfected?
var/disinfected = FALSE
var/created = 0
/// number of wounds of this type
var/amount = 1
/// amount of germs in the wound
var/germ_level = 0
/// the organ the wound is on, if on an organ
var/obj/item/organ/external/parent_organ
/* These are defined by the wound type and should not be changed */
/// stages such as "cut", "deep cut", etc.
var/list/stages
/// maximum stage at which bleeding should still happen. Beyond this stage bleeding is prevented.
var/max_bleeding_stage = 0
/// String (One of `DAMAGE_TYPE_*`). The wound's injury type.
var/damage_type = INJURY_TYPE_CUT
/// whether this wound needs a bandage/salve to heal at all
/// the maximum amount of damage that this wound can have and still autoheal
var/autoheal_cutoff = 15
// helper lists
var/list/embedded_objects
var/tmp/list/desc_list = list()
var/tmp/list/damage_list = list()
/datum/wound/New(damage, obj/item/organ/external/organ = null)
created = world.time
// reading from a list("stage" = damage) is pretty difficult, so build two separate
// lists from them instead
for(var/V in stages)
desc_list += V
damage_list += stages[V]
src.damage = damage
// initialize with the appropriate stage
src.init_stage(damage)
bleed_timer += damage
if(istype(organ))
parent_organ = organ
/datum/wound/Destroy()
if(parent_organ)
LAZYREMOVE(parent_organ.wounds, src)
parent_organ = null
LAZYCLEARLIST(embedded_objects)
. = ..()
///Returns TRUE if there's a next stage, FALSE otherwise
/datum/wound/proc/init_stage(initial_damage)
current_stage = length(stages)
while(src.current_stage > 1 && src.damage_list[current_stage-1] <= initial_damage / src.amount)
src.current_stage--
src.min_damage = damage_list[current_stage]
src.desc = desc_list[current_stage]
///The amount of damage per wound
/datum/wound/proc/wound_damage()
return src.damage / src.amount
/datum/wound/proc/can_autoheal()
if(LAZYLEN(embedded_objects))
return FALSE
return (wound_damage() <= autoheal_cutoff) ? TRUE : is_treated()
/// Checks whether the wound has been appropriately treated
/datum/wound/proc/is_treated()
if(!LAZYLEN(embedded_objects))
switch(damage_type)
if(INJURY_TYPE_BRUISE, INJURY_TYPE_CUT, INJURY_TYPE_PIERCE)
return bandaged
if(INJURY_TYPE_BURN)
return salved
/// Checks whether other other can be merged into src.
/datum/wound/proc/can_merge(datum/wound/other)
if (other.type != src.type) return FALSE
if (other.current_stage != src.current_stage) return FALSE
if (other.damage_type != src.damage_type) return FALSE
if (!(other.can_autoheal()) != !(src.can_autoheal())) return FALSE
if (!(other.bandaged) != !(src.bandaged)) return FALSE
if (!(other.clamped) != !(src.clamped)) return FALSE
if (!(other.salved) != !(src.salved)) return FALSE
if (!(other.disinfected) != !(src.disinfected)) return FALSE
if (other.parent_organ != parent_organ) return FALSE
return TRUE
/datum/wound/proc/merge_wound(datum/wound/other)
if(LAZYLEN(other.embedded_objects))
LAZYDISTINCTADD(src.embedded_objects, other.embedded_objects)
src.damage += other.damage
src.amount += other.amount
src.bleed_timer += other.bleed_timer
src.bleed_timer_increment += other.bleed_timer_increment
src.germ_level = max(src.germ_level, other.germ_level)
src.created = max(src.created, other.created) //take the newer created time
qdel(other)
/// checks if wound is considered open for external infections
/// untreated cuts (and bleeding bruises) and burns are possibly infectable, chance higher if wound is bigger
/datum/wound/proc/infection_check()
if (damage < 10) //small cuts, tiny bruises, and moderate burns shouldn't be infectable.
return FALSE
if (is_treated() && damage < 25) //anything less than a flesh wound (or equivalent) isn't infectable if treated properly
return FALSE
if (disinfected)
germ_level = 0 //reset this, just in case
return FALSE
if (damage_type == INJURY_TYPE_BRUISE && !bleeding()) //bruises only infectable if bleeding
return FALSE
var/dam_coef = round(damage/10)
switch (damage_type)
if (INJURY_TYPE_BRUISE)
return prob(dam_coef*5)
if (INJURY_TYPE_BURN)
return prob(dam_coef*10)
if (INJURY_TYPE_CUT)
return prob(dam_coef*20)
return FALSE
/datum/wound/proc/bandage()
bandaged = TRUE
/datum/wound/proc/salve()
salved = TRUE
/datum/wound/proc/disinfect()
disinfected = TRUE
/// Heal the given amount of damage, and if the given amount of damage was more than what esd needed to be healed, return how much heal was left
/datum/wound/proc/heal_damage(amount)
if(LAZYLEN(embedded_objects))
return amount // heal nothing
if(parent_organ)
if (damage_type == INJURY_TYPE_BURN && !(parent_organ.burn_ratio < 100))
return amount // We don't want to heal wounds on irreparable organs
else if(!(parent_organ.brute_ratio < 100))
return amount
var/healed_damage = min(src.damage, amount)
amount -= healed_damage
src.damage -= healed_damage
while(src.wound_damage() < damage_list[current_stage] && current_stage < length(src.desc_list))
current_stage++
desc = desc_list[current_stage]
src.min_damage = damage_list[current_stage]
// return amount of healing still leftover, can be used for other wounds
return amount
/// Opens the wound again
/datum/wound/proc/open_wound(damage)
src.damage += damage
bleed_timer += damage
while(src.current_stage > 1 && src.damage_list[current_stage-1] <= src.damage / src.amount)
src.current_stage--
src.desc = desc_list[current_stage]
src.min_damage = damage_list[current_stage]
src.bandaged = FALSE
///returns whether this wound can absorb the given amount of damage.
///this will prevent large amounts of damage being trapped in less severe wound types
/datum/wound/proc/can_worsen(damage_type, damage)
if (src.damage_type != damage_type)
return FALSE //incompatible damage types
if (src.amount > 1)
return FALSE //merged wounds cannot be worsened.
//with 1.5*, a shallow cut will be able to carry at most 30 damage,
//37.5 for a deep cut
//52.5 for a flesh wound, etc.
var/max_wound_damage = 1.5*src.damage_list[1]
if (src.damage + damage > max_wound_damage)
return FALSE
return TRUE
/datum/wound/proc/bleeding()
for(var/obj/item/thing in embedded_objects)
if(thing.w_class > WEIGHT_CLASS_SMALL)
return FALSE
if (bandaged||clamped)
return FALSE
return ((bleed_timer > 0 || wound_damage() > bleed_threshold) && current_stage <= max_bleeding_stage)
/// Called in organ_external.dm update_damages, this will update the limb's status to bleeding, and lowers the bleed_timer if applicable
/datum/wound/proc/handle_bleeding(var/mob/victim, var/obj/item/organ/external/limb)
limb.status |= ORGAN_BLEEDING
var/can_lower_bleed = TRUE
if(HAS_TRAIT(victim, TRAIT_DISABILITY_HEMOPHILIA_MAJOR)) // major will NEVER lower
can_lower_bleed = FALSE
else if(HAS_TRAIT(victim, TRAIT_DISABILITY_HEMOPHILIA) && bleed_timer_increment % 2 != 0) // basic will lower half as fast
can_lower_bleed = FALSE
if(can_lower_bleed)
bleed_timer--
bleed_timer_increment++
/datum/wound/proc/close()
return
+291
View File
@@ -0,0 +1,291 @@
/** WOUND DEFINITIONS **/
//Note that the MINIMUM damage before a wound can be applied should correspond to
//the damage amount for the stage with the same name as the wound.
//e.g. /datum/wound/cut/deep should only be applied for 15 damage and up,
//because in it's stages list, "deep cut" = 15.
/proc/get_wound_type(type = INJURY_TYPE_CUT, damage)
switch(type)
if(INJURY_TYPE_CUT)
switch(damage)
if(70 to INFINITY)
return /datum/wound/cut/massive
if(60 to 70)
return /datum/wound/cut/gaping_big
if(50 to 60)
return /datum/wound/cut/gaping
if(25 to 50)
return /datum/wound/cut/flesh
if(15 to 25)
return /datum/wound/cut/deep
if(0 to 15)
return /datum/wound/cut/small
if(INJURY_TYPE_PIERCE)
switch(damage)
if(60 to INFINITY)
return /datum/wound/puncture/massive
if(50 to 60)
return /datum/wound/puncture/gaping_big
if(30 to 50)
return /datum/wound/puncture/gaping
if(15 to 30)
return /datum/wound/puncture/flesh
if(0 to 15)
return /datum/wound/puncture/small
if(INJURY_TYPE_BRUISE)
return /datum/wound/bruise
if(INJURY_TYPE_BURN, INJURY_TYPE_LASER)
switch(damage)
if(50 to INFINITY)
return /datum/wound/burn/carbonised
if(40 to 50)
return /datum/wound/burn/deep
if(30 to 40)
return /datum/wound/burn/severe
if(15 to 30)
return /datum/wound/burn/large
if(0 to 15)
return /datum/wound/burn/moderate
return null //no wound
/** CUTS **/
/datum/wound/cut
bleed_threshold = 5
damage_type = INJURY_TYPE_CUT
/datum/wound/cut/bandage()
..()
if(!autoheal_cutoff)
autoheal_cutoff = initial(autoheal_cutoff)
/datum/wound/cut/close()
current_stage = max_bleeding_stage + 1
desc = desc_list[current_stage]
min_damage = damage_list[current_stage]
if(damage > min_damage)
heal_damage(damage-min_damage)
/datum/wound/cut/small
// link wound descriptions to amounts of damage
// Minor cuts have max_bleeding_stage set to the stage that bears the wound type's name.
// The major cut types have the max_bleeding_stage set to the clot stage (which is accordingly given the "blood soaked" descriptor).
max_bleeding_stage = 3
stages = list(
"ugly ripped cut" = 20,
"ripped cut" = 10,
"cut" = 5,
"healing cut" = 2,
"small scab" = 0
)
/datum/wound/cut/deep
max_bleeding_stage = 3
stages = list(
"ugly deep ripped cut" = 25,
"deep ripped cut" = 20,
"deep cut" = 15,
"clotted cut" = 8,
"scab" = 2,
"fresh skin" = 0
)
/datum/wound/cut/flesh
max_bleeding_stage = 4
stages = list(
"ugly ripped flesh wound" = 35,
"ugly flesh wound" = 30,
"flesh wound" = 25,
"blood soaked clot" = 15,
"large scab" = 5,
"fresh skin" = 0
)
/datum/wound/cut/gaping
max_bleeding_stage = 3
stages = list(
"gaping wound" = 50,
"large blood soaked clot" = 25,
"blood soaked clot" = 15,
"small angry scar" = 5,
"small straight scar" = 0
)
/datum/wound/cut/gaping_big
max_bleeding_stage = 3
stages = list(
"big gaping wound" = 60,
"healing gaping wound" = 40,
"large blood soaked clot" = 25,
"large angry scar" = 10,
"large straight scar" = 0
)
/datum/wound/cut/massive
max_bleeding_stage = 3
stages = list(
"massive wound" = 70,
"massive healing wound" = 50,
"massive blood soaked clot" = 25,
"massive angry scar" = 10,
"massive jagged scar" = 0
)
/** PUNCTURES **/
/datum/wound/puncture
bleed_threshold = 10
damage_type = INJURY_TYPE_PIERCE
/datum/wound/puncture/can_worsen(damage_type, damage)
return FALSE //puncture wounds cannot be enlargened
/datum/wound/puncture/small
max_bleeding_stage = 2
stages = list(
"puncture" = 5,
"healing puncture" = 2,
"small scab" = 0
)
/datum/wound/puncture/flesh
max_bleeding_stage = 2
stages = list(
"puncture wound" = 15,
"blood soaked clot" = 5,
"large scab" = 2,
"small round scar" = 0
)
/datum/wound/puncture/gaping
max_bleeding_stage = 3
stages = list(
"gaping hole" = 30,
"large blood soaked clot" = 15,
"blood soaked clot" = 10,
"small angry scar" = 5,
"small round scar" = 0
)
/datum/wound/puncture/gaping_big
max_bleeding_stage = 3
stages = list(
"big gaping hole" = 50,
"healing gaping hole" = 20,
"large blood soaked clot" = 15,
"large angry scar" = 10,
"large round scar" = 0
)
/datum/wound/puncture/massive
max_bleeding_stage = 3
stages = list(
"massive wound" = 60,
"massive healing wound" = 30,
"massive blood soaked clot" = 25,
"massive angry scar" = 10,
"massive jagged scar" = 0
)
/** BRUISES **/
/datum/wound/bruise
stages = list(
"monumental bruise" = 80,
"huge bruise" = 50,
"large bruise" = 30,
"moderate bruise" = 20,
"small bruise" = 10,
"tiny bruise" = 5
)
bleed_threshold = 20
max_bleeding_stage = 3 //only large bruise and above can bleed.
autoheal_cutoff = 30
damage_type = INJURY_TYPE_BRUISE
/** BURNS **/
/datum/wound/burn
damage_type = INJURY_TYPE_BURN
max_bleeding_stage = 0
/datum/wound/burn/bleeding()
return FALSE
/datum/wound/burn/moderate
stages = list(
"ripped burn" = 10,
"moderate burn" = 5,
"healing moderate burn" = 2,
"fresh skin" = 0
)
/datum/wound/burn/large
stages = list(
"ripped large burn" = 20,
"large burn" = 15,
"healing large burn" = 5,
"fresh skin" = 0
)
/datum/wound/burn/severe
stages = list(
"ripped severe burn" = 35,
"severe burn" = 30,
"healing severe burn" = 10,
"burn scar" = 0
)
/datum/wound/burn/deep
stages = list(
"ripped deep burn" = 45,
"deep burn" = 40,
"healing deep burn" = 15,
"large burn scar" = 0
)
/datum/wound/burn/carbonised
stages = list(
"carbonised area" = 50,
"healing carbonised area" = 20,
"massive burn scar" = 0
)
/** EXTERNAL ORGAN LOSS **/
/datum/wound/lost_limb
var/limb_tag
/datum/wound/lost_limb/New(obj/item/organ/external/lost_limb, losstype, clean)
var/damage_amt = lost_limb.max_damage
if(clean) damage_amt /= 2
limb_tag = lost_limb.organ_tag
switch(losstype)
if(DROPLIMB_EDGE, DROPLIMB_BLUNT)
damage_type = INJURY_TYPE_CUT
if(BP_IS_ROBOTIC(lost_limb))
max_bleeding_stage = -1 //Robotic stumps do not bleed
bleed_threshold = INFINITY
stages = list("mangled robotic socket" = 0)
else
max_bleeding_stage = 3 //clotted stump and above can bleed.
stages = list(
"ripped stump" = damage_amt*1.3,
"bloody stump" = damage_amt,
"clotted stump" = damage_amt*0.5,
"scarred stump" = 0
)
if(DROPLIMB_BURN)
damage_type = INJURY_TYPE_BURN
stages = list(
"ripped charred stump" = damage_amt*1.3,
"charred stump" = damage_amt,
"scarred stump" = damage_amt*0.5,
"scarred stump" = 0
)
..(damage_amt)
/datum/wound/lost_limb/can_merge(var/datum/wound/other)
return 0 //cannot be merged
@@ -165,11 +165,10 @@
var/obj/item/organ/external/E = A
if(BP_IS_ROBOTIC(E))
continue
if (E.wounds.len)
for(var/datum/wound/W in E.wounds)
if(W.germ_level < INFECTION_LEVEL_ONE)
W.germ_level = INFECTION_LEVEL_ONE
W.germ_level += rand(10, 50)
for(var/datum/wound/W in E.wounds)
if(W.germ_level < INFECTION_LEVEL_ONE)
W.germ_level = INFECTION_LEVEL_ONE
W.germ_level += rand(10, 50)
/turf/simulated/floor/exoplanet/water/shallow/moghes
icon = 'icons/turf/flooring/exoplanet/moghes.dmi'
+1 -1
View File
@@ -578,7 +578,7 @@ By design, d1 is the smallest direction and d2 is the highest
break
user.visible_message(SPAN_NOTICE("\The [user] barely manages to stitch \a [W.desc] on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You barely manage to stitch \a [W.desc] on [target_mob]'s [affecting.name].") )
W.bandage("cable-stitched")
W.bandage()
use(10)
affecting.add_pain(25)
if(prob(min(30 + (germ_level/5), 65))) //Less chance of infection if you clean the coil. Coil's germ level is set to GERM_LEVEL_AMBIENT
@@ -0,0 +1,66 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: GeneralCamo
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- code_imp: "Damage ratios now use integers instead of decimals, reducing the impact of floating point rounding errors."
- bugfix: "Implants now remain in amputated limbs."
- rscadd: "Amputated external organs now retain their implants and internal organs."
- code_imp: "Embedded objects are now stored in wounds, not in limbs (at least, not directly)."
- balance: "Larger embedded objects now halt bleeding in wounds."
- balance: "Embedded objects now halt healing in wounds."
- code_imp: "Untangled damage flags and injury type flags."
- balance: "Pinning no longer blocks yanking out items."
- balance: "Generalise the autoheal threshold so wounds don't autoheal when a limb has taken either too much overall damage or the mob has taken significant damage."