mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-23 12:27:08 +01:00
- reagents are now singletons - most of reagent holder datums are refactored.
445 lines
14 KiB
Plaintext
445 lines
14 KiB
Plaintext
/**
|
|
* Creates a wound on this organ.
|
|
*
|
|
* todo: better documentation
|
|
*/
|
|
/obj/item/organ/external/proc/create_wound(var/type = WOUND_TYPE_CUT, var/damage)
|
|
if(damage == 0)
|
|
return
|
|
|
|
//moved this before the open_wound check so that having many small wounds for example doesn't somehow protect you from taking internal damage (because of the return)
|
|
//Possibly trigger an internal wound, too.
|
|
var/local_damage = brute_dam + burn_dam + damage
|
|
if((damage > 15) && (type != WOUND_TYPE_BURN) && (local_damage > 30) && prob(damage) && (robotic < ORGAN_ROBOT) && !(species.species_flags & NO_BLOOD))
|
|
create_specific_wound(/datum/wound/internal_bleeding, min(damage - 15, 15))
|
|
if(!(behaviour_flags & BODYPART_SILENT_WOUNDS))
|
|
owner.custom_pain("You feel something rip in your [name]!", 50)
|
|
|
|
//Burn damage can cause fluid loss due to blistering and cook-off
|
|
|
|
if((damage > 5 || damage + burn_dam >= 15) && type == WOUND_TYPE_BURN && (robotic < ORGAN_ROBOT) && !(species.species_flags & NO_BLOOD))
|
|
var/fluid_loss = 0.4 * (damage/(owner.getMaxHealth() - owner.getMinHealth())) * owner.species.blood_volume*(1 - owner.species.blood_level_fatal)
|
|
owner.erase_blood(fluid_loss)
|
|
|
|
// first check whether we can widen an existing wound
|
|
if(length(wounds) > 0 && prob(max(50+(wound_tally-1)*10,90)))
|
|
if((type == WOUND_TYPE_CUT || type == WOUND_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 as anything in wounds)
|
|
if (W.can_worsen(type, damage))
|
|
compatible_wounds += W
|
|
|
|
if(compatible_wounds.len)
|
|
var/datum/wound/W = pick(compatible_wounds)
|
|
W.open_wound(damage)
|
|
if(!(behaviour_flags & BODYPART_SILENT_WOUNDS) && prob(25))
|
|
if(robotic >= ORGAN_ROBOT)
|
|
owner.visible_message("<span class='danger'>The damage to [owner.name]'s [name] worsens.</span>",\
|
|
"<span class='danger'>The damage to your [name] worsens.</span>",\
|
|
"<span class='danger'>You hear the screech of abused metal.</span>")
|
|
else
|
|
owner.visible_message("<span class='danger'>The wound on [owner.name]'s [name] widens with a nasty ripping noise.</span>",\
|
|
"<span class='danger'>The wound on your [name] widens with a nasty ripping noise.</span>",\
|
|
"<span class='danger'>You hear a nasty ripping noise, as if flesh is being torn apart.</span>")
|
|
return
|
|
|
|
//Creating wound
|
|
var/wound_type = get_wound_type(type, damage)
|
|
|
|
if(wound_type)
|
|
var/datum/wound/W = new wound_type(damage, behaviour_flags & BODYPART_NO_INFECTION)
|
|
|
|
//Check whether we can add the wound to an existing wound
|
|
for(var/datum/wound/other as anything in wounds)
|
|
if(other.can_merge(W))
|
|
other.merge_wound(W)
|
|
W = null // to signify that the wound was added
|
|
break
|
|
if(W)
|
|
LAZYADD(wounds, W)
|
|
|
|
/**
|
|
* Creates a specific kind of wound on this organ.
|
|
*
|
|
* This only creates the wound, it does not do damage side effects,
|
|
* like vaporizing blood with burns, etc
|
|
*
|
|
* Such side effects should not be in the procs for making wounds!
|
|
*
|
|
* If you need to modify variables other than "damage", grab the returned wound.
|
|
*
|
|
* @params
|
|
* * path - typepath of /datum/wound to create.
|
|
* * damage - amount of damage it should have.
|
|
* * updating - update damages?
|
|
*
|
|
* @return the /datum/wound created, *or* the /datum/wound merged, *or* null if it was rejected.
|
|
*/
|
|
/obj/item/organ/external/proc/create_specific_wound(path, damage, updating = TRUE)
|
|
ASSERT(ispath(path, /datum/wound))
|
|
|
|
var/datum/wound/creating = new path(damage, behaviour_flags & BODYPART_NO_INFECTION)
|
|
var/datum/wound/merged
|
|
|
|
for(var/datum/wound/other as anything in wounds)
|
|
if(other.can_merge(creating))
|
|
other.merge_wound(creating)
|
|
merged = other
|
|
break
|
|
|
|
if(isnull(merged))
|
|
// didn't merge, add
|
|
LAZYADD(wounds, creating)
|
|
. = creating
|
|
else
|
|
// merged, return merged
|
|
// we don't manually qdel creating - hopefully no one actually does something
|
|
// deranged down the line like making wounds reference externally!
|
|
. = merged
|
|
|
|
if(updating)
|
|
update_damages()
|
|
|
|
/**
|
|
* Immediately cures a specific typepath of wound, or a specific instance of wound
|
|
*
|
|
* @params
|
|
* * path - path of wound to cure, all subtypes count!
|
|
* * all - cure all instances, or just one?
|
|
* * updating - update damages?
|
|
*
|
|
* @return TRUE / FALSE based on if anything was removed.
|
|
*/
|
|
/obj/item/organ/external/proc/cure_specific_wound(datum/wound/path_or_instance, all = FALSE, updating = TRUE)
|
|
. = FALSE
|
|
// todo: remove the assert / is in check, free performance, only here to prevent accidental misuse for now.
|
|
ASSERT(ispath(path_or_instance, /datum/wound))
|
|
for(var/datum/wound/W in wounds)
|
|
if(istype(W, path_or_instance))
|
|
wounds -= W
|
|
. = TRUE
|
|
if(all)
|
|
break
|
|
|
|
if(!.)
|
|
return
|
|
|
|
if(updating)
|
|
update_damages()
|
|
|
|
/**
|
|
* Immediately cures a wound instance.
|
|
*
|
|
* Use this proc from within loops over the wounds list.
|
|
*
|
|
* Warning: We do not check if the wound exists.s
|
|
*
|
|
* @params
|
|
* * wound - the wound to cure
|
|
* * updating - update damages?
|
|
*/
|
|
/obj/item/organ/external/proc/cure_exact_wound(datum/wound/wound, updating = TRUE)
|
|
wounds -= wound
|
|
|
|
if(updating)
|
|
update_damages()
|
|
|
|
/**
|
|
* Wound datums
|
|
*
|
|
* Does as its name implies - tracks a wound, or a group of similar wounds,
|
|
* on an /obj/item/organ/external.
|
|
*/
|
|
/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
|
|
// 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 = 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
|
|
// internal wounds can only be fixed through surgery
|
|
var/internal = FALSE
|
|
// maximum stage at which bleeding should still happen. Beyond this stage bleeding is prevented.
|
|
var/max_bleeding_stage = 0
|
|
// one of WOUND_TYPE_CUT, WOUND_TYPE_PIERCE, WOUND_TYPE_BRUISE, WOUND_TYPE_BURN
|
|
var/wound_type = WOUND_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 = 10
|
|
|
|
// whether this wound starts infected regardless of damage level
|
|
var/forced_infected = FALSE
|
|
|
|
|
|
// helper lists
|
|
var/tmp/list/desc_list = list()
|
|
var/tmp/list/damage_list = list()
|
|
|
|
var/can_infect = TRUE
|
|
|
|
/datum/wound/New(damage, _can_infect = TRUE)
|
|
|
|
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
|
|
|
|
can_infect = _can_infect
|
|
|
|
// returns 1 if there's a next stage, 0 otherwise
|
|
/datum/wound/proc/init_stage(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(is_treated())
|
|
return TRUE
|
|
if(src.wound_damage() <= autoheal_cutoff)
|
|
if(created + 10 MINUTES > world.time) // Wounds don't autoheal for ten minutes if not bandaged.
|
|
return FALSE
|
|
return TRUE
|
|
|
|
// checks whether the wound has been appropriately treated
|
|
/datum/wound/proc/is_treated()
|
|
if(wound_type == WOUND_TYPE_BRUISE || wound_type == WOUND_TYPE_CUT || wound_type == WOUND_TYPE_PIERCE)
|
|
return bandaged
|
|
else if(wound_type == WOUND_TYPE_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.wound_type != src.wound_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(datum/wound/other)
|
|
src.damage += other.damage
|
|
src.amount += other.amount
|
|
src.bleed_timer += other.bleed_timer
|
|
src.germ_level = max(src.germ_level, other.germ_level)
|
|
src.created = max(src.created, other.created) // Take the newer created time.
|
|
|
|
/// Forces an infection, and bleeding regardless of damage or stage.
|
|
/datum/wound/proc/force_infect()
|
|
bleed_threshold = 4 //Will always start bleeding, making the infection worse if untreated
|
|
forced_infected = TRUE
|
|
germ_level = INFECTION_LEVEL_ONE + 1
|
|
|
|
// 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(!can_infect)
|
|
return FALSE
|
|
if (disinfected)
|
|
if(germ_level > INFECTION_LEVEL_ONE)
|
|
germ_level = 0 //reset this, just in case
|
|
forced_infected = FALSE
|
|
return FALSE
|
|
if (damage < 10 && !forced_infected) //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 (wound_type == WOUND_TYPE_BRUISE && !bleeding()) //bruises only infectable if bleeding
|
|
return FALSE
|
|
|
|
|
|
if(forced_infected) //This wound is forced to be infected, circumventing damage requirements, check after making sure it's not bleeding and isn't disinfected
|
|
return TRUE
|
|
|
|
var/dam_coef = round(damage/10)
|
|
switch (wound_type)
|
|
if (WOUND_TYPE_BRUISE)
|
|
return prob(dam_coef*5)
|
|
if (WOUND_TYPE_BURN)
|
|
return prob(dam_coef*10)
|
|
if (WOUND_TYPE_CUT)
|
|
return prob(dam_coef*20)
|
|
|
|
return FALSE
|
|
|
|
/datum/wound/proc/bandage()
|
|
bandaged = 1
|
|
|
|
/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)
|
|
if(src.internal && !heals_internal)
|
|
// heal nothing
|
|
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 < 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]
|
|
|
|
// 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(woud_type, damage)
|
|
if (src.wound_type != woud_type)
|
|
return 0 //incompatible damage types
|
|
|
|
if (src.amount > 1)
|
|
return 0//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 0
|
|
|
|
return 1
|
|
|
|
/datum/wound/proc/bleeding()
|
|
if (src.internal)
|
|
return 0 // internal wounds don't bleed in the sense of this function
|
|
|
|
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
|
|
|
|
/** 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 = WOUND_TYPE_CUT, damage)
|
|
switch(type)
|
|
if(WOUND_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(WOUND_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(WOUND_TYPE_BRUISE)
|
|
return /datum/wound/bruise
|
|
if(WOUND_TYPE_BURN)
|
|
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
|