Skip to content

Commit

Permalink
Separates bleeding from damage, gauze now STOPS bleeding rather than …
Browse files Browse the repository at this point in the history
…postponing it (#3009)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->


![image](https://github.com/shiptest-ss13/Shiptest/assets/24857008/d5162c66-c24c-4115-8f17-b71fe2a7c97e)


![image](https://github.com/shiptest-ss13/Shiptest/assets/24857008/0035fc3d-ae4f-4bc8-b8a2-fab9e1652365)

Bleeding rate is currently the same as it used to be (0.013 bleeding per
damage) but can be increased beyond a limb's maximum damage from further
attacks. Bleeding from damage caps at 2 per limb so exsanguinating
someone isn't as easy as dumping lead into them until their blood falls
out, and is hard-capped at 30 per limb for more direct sources such as
throat cutting and heparin
Blunt weapons require a force of 10 or higher, and for the limb to have
25 damage to cause bleeding
Sharp weapons (including projectiles) need a force of 5 or higher, and
for the limb to have 10 damage to cause bleeding
Heparin now causes existing bleeding to get worse, rather than magically
creating internal bleeding

Bleeding is addressed through gauze, tape, cauterization or chemicals,
with the former three halting current bleeding while it is being
treated. Better equipment (primarily real medical gauze) heals faster.

Numbers are non-final and speculative

## Why It's Good For The Game

Causes bleeding to actually come from somewhere and be similarly
addressed directly, gauze now exists to actually stop bleeding instead
of being re-applied until the brute damage is fixed

## Changelog

:cl:
tweak: bleeding is now stored in the limbs, functioning similarly to
bone breaking. Taking damage over a certain threshold and amount (lower
for sharp weapons) will cause part of it to be turned into bleeding.
tweak: gauze, tape, and bleeding suppression are similarly no longer
abstracted into a "bleed suppression" value
rscadd: you can now cauterize bleeding with a lighter. Which is cool.
rscadd: you can now also cauterize bleeding with suit storage
decontamination. Which is hot.
rscadd: examine and examine closely will show whether or not someone is
visibly bleeding or bandaged. This means people who are both will show
both. Examine closely additionally shows which limbs are currently
bleeding.
tweak: heparin now causes existing bleeding to worsen, instead of
causing bleeding on its own
tweak: you can no longer cut the throat of someone who's head has been
lopped off
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put
your name to the right of the first :cl: if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

---------

Signed-off-by: Theos <[email protected]>
  • Loading branch information
SomeguyManperson authored Jun 8, 2024
1 parent 0449e6d commit c9bf77d
Show file tree
Hide file tree
Showing 39 changed files with 335 additions and 95 deletions.
5 changes: 5 additions & 0 deletions code/__DEFINES/dcs/signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@
/* #define SPEECH_IGNORE_SPAM 6
#define SPEECH_FORCED 7 */

///from /mob/living/life()
#define COMSIG_MOB_LIFE "mob_life"

///from /mob/say_dead(): (mob/speaker, message)
#define COMSIG_MOB_DEADSAY "mob_deadsay"
#define MOB_DEADSAY_SIGNAL_INTERCEPT (1<<0)
Expand Down Expand Up @@ -420,6 +423,8 @@
///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment
#define COMSIG_LIVING_ATTACH_LIMB "living_attach_limb"
#define COMPONENT_NO_ATTACH 1
///from base of /obj/item/bodypart/proc/drop_limb(): (special)
#define COMSIG_LIVING_DROP_LIMB "living_drop_limb"
///from base of mob/living/set_buckled(): (new_buckled)
#define COMSIG_LIVING_SET_BUCKLED "living_set_buckled"

Expand Down
7 changes: 7 additions & 0 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
#define BLOOD_VOLUME_BAD 224
#define BLOOD_VOLUME_SURVIVE 122

// Bloodloss
#define BLOOD_LOSS_MAXIMUM 30
#define BLOOD_LOSS_DAMAGE_MAXIMUM 2
#define BLOOD_LOSS_DAMAGE_BASE 0.013
#define BLOOD_CAUTERIZATION_RATIO 10
#define BLOOD_CAUTERIZATION_DAMAGE_RATIO 300

//Sizes of mobs, used by mob/living/var/mob_size
#define MOB_SIZE_TINY 0
#define MOB_SIZE_SMALL 1
Expand Down
2 changes: 1 addition & 1 deletion code/_onclick/item_attack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
var/armor_value = run_armor_check(attack_flag = "melee", armour_penetration = I.armour_penetration) //WS Edit - Simplemobs can have armor
send_item_attack_message(I, user)
if(I.force)
apply_damage(I.force, I.damtype, break_modifier = I.force, blocked = armor_value) //Bone break modifier = item force
apply_damage(I.force, I.damtype, break_modifier = I.force, blocked = armor_value, sharpness = I.get_sharpness()) //Bone break modifier = item force
if(I.damtype == BRUTE)
if(prob(33))
I.add_mob_blood(src)
Expand Down
60 changes: 60 additions & 0 deletions code/datums/components/bandage.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#define TREATMENT_DAMAGE_MOD 2

/datum/component/bandage
/// How fast do we stop bleeding?
var/bleed_reduction = 0
/// How many healing ticks will this bandage apply? Reduced by incoming damage and current bleeding
var/lifespan = 300
var/bandage_name = "gauze"
/// The person this bandage is applied to
var/mob/living/mummy

/datum/component/bandage/Initialize(_bleed_reduction, _lifespan, _bandage_name)
if(!istype(parent, /obj/item/bodypart))
return COMPONENT_INCOMPATIBLE
var/obj/item/bodypart/BP = parent
mummy = BP.owner
if(!mummy)
return COMPONENT_INCOMPATIBLE
if(_bleed_reduction)
bleed_reduction = _bleed_reduction
if(_lifespan)
lifespan = _lifespan
if(_bandage_name)
bandage_name = _bandage_name
RegisterSignal(mummy, COMSIG_MOB_APPLY_DAMGE, PROC_REF(check_damage))
RegisterSignal(mummy, COMSIG_MOB_LIFE, PROC_REF(bandage_effects))
RegisterSignal(parent, COMSIG_LIVING_DROP_LIMB, PROC_REF(drop_bandage))

/// Checks if damage to the owner is applied to this limb and reduces lifespan (perforated bandages dont work as well)
/datum/component/bandage/proc/check_damage(attacker, damage, damagetype = BRUTE, def_zone = null)
SIGNAL_HANDLER

if(parent != mummy.get_bodypart(check_zone(def_zone)))
return
lifespan -= damage / 100 * initial(lifespan) * TREATMENT_DAMAGE_MOD //take incoming damage as a % of durability
if(lifespan <= 0)
drop_bandage()

/// Handles healing effects and passive lifespan usage
/datum/component/bandage/proc/bandage_effects()
SIGNAL_HANDLER

var/obj/item/bodypart/heal_target = parent
lifespan -= 1 + heal_target.bleeding // particularly nasty bleeding can burn through dressing faster
heal_target.adjust_bleeding(-bleed_reduction)
if(lifespan <= 0 || !heal_target.bleeding) //remove treatment once it's no longer able to treat
drop_bandage(TRUE)

/// Handles deleting the component when the bandage runs out of lifespan or finishes healing. Special = bandage didn't get torn off
/datum/component/bandage/proc/drop_bandage(special = FALSE)
SIGNAL_HANDLER

var/obj/item/bodypart/BP = parent
if(special)
to_chat(mummy, span_notice("The [bandage_name] on your [parse_zone(BP.body_zone)] has [BP.bleeding ? "done what it can" : "stopped the bleeding"]."))
else
to_chat(mummy, span_warning("The [bandage_name] on your [parse_zone(BP.body_zone)] is damaged beyond use!"))
qdel(src)

#undef TREATMENT_DAMAGE_MOD
8 changes: 7 additions & 1 deletion code/datums/components/butchering.dm
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@
"<span class='warning'>Their neck has already been already cut, you can't make the bleeding any worse!</span>")
return

var/obj/item/bodypart/throat_in_question = H.get_bodypart(BODY_ZONE_HEAD)
if(!throat_in_question)
user.show_message("<span class='warning'>[H]... doesn't have a neck.</span>", MSG_VISUAL, \
"<span class='warning'>They don't seem to have a neck to cut.</span>")
return

H.visible_message("<span class='danger'>[user] slits [H]'s throat!</span>", \
"<span class='userdanger'>[user] slits your throat...</span>")
log_combat(user, H, "finishes slicing the throat of")
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD)
H.bleed_rate = clamp(H.bleed_rate + 20, 0, 30)
throat_in_question.adjust_bleeding(20)
H.apply_status_effect(/datum/status_effect/neck_slice)

/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
Expand Down
2 changes: 1 addition & 1 deletion code/datums/diseases/advance/symptoms/flesh_eating.dm
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Bonus
if(bleed)
if(ishuman(M))
var/mob/living/carbon/human/H = M
H.bleed_rate += 5 * power
H.cause_bleeding(5 * power)
return 1

/*
Expand Down
3 changes: 2 additions & 1 deletion code/datums/status_effects/debuffs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@

/datum/status_effect/neck_slice/tick()
var/mob/living/carbon/human/H = owner
if(H.stat == DEAD || H.bleed_rate <= 8)
var/obj/item/bodypart/throat_in_question = H.get_bodypart(BODY_ZONE_HEAD)
if(H.stat == DEAD || throat_in_question?.bleeding <= 8)
H.remove_status_effect(/datum/status_effect/neck_slice)
if(prob(10))
H.emote(pick("gasp", "gag", "choke"))
Expand Down
2 changes: 1 addition & 1 deletion code/game/machinery/medical_kiosk.dm
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
sickness_data = "\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]"

if(altPatient.has_dna()) //Blood levels Information
if(altPatient.bleed_rate)
if(LAZYLEN(altPatient.get_bleeding_parts()))
bleed_status = "Patient is currently bleeding!"
if(blood_percent <= 80)
blood_warning = " Patient has low blood levels. Seek a large meal, or iron supplements."
Expand Down
6 changes: 6 additions & 0 deletions code/game/machinery/suit_storage_unit.dm
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,12 @@
else
visible_message(span_warning("[src]'s door slides open, barraging you with the nauseating smell of charred flesh."))
mob_occupant.radiation = 0
if(iscarbon(mob_occupant))
var/mob/living/carbon/bacon = mob_occupant
for(var/obj/item/bodypart/grilling as anything in bacon.get_bleeding_parts(TRUE))
if(!grilling.can_bandage())
continue
grilling.apply_bandage(0.005, 600, "cauterization")
playsound(src, 'sound/machines/airlocks/standard/close.ogg', 25, TRUE)
var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such.
if(suit)
Expand Down
2 changes: 1 addition & 1 deletion code/game/objects/items/devices/scanners.dm
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ GENE SCANNER
if(blood_id)
if(ishuman(C))
var/mob/living/carbon/human/H = C
if(H.bleed_rate)
if(LAZYLEN(H.get_bleeding_parts()))
render_list += "<span class='alert ml-1'><b>Subject is bleeding!</b></span>\n"
var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
var/blood_type = C.dna.blood_type.name
Expand Down
24 changes: 14 additions & 10 deletions code/game/objects/items/stacks/medical.dm
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,13 @@

/obj/item/stack/medical/gauze
name = "medical gauze"
desc = "A roll of elastic cloth that is extremely effective at stopping bleeding, but does not heal wounds."
desc = "A roll of elastic cloth that is extremely effective at stopping bleeding and slowly heals wounds."
gender = PLURAL
singular_name = "medical gauze"
icon_state = "gauze"
apply_sounds = list('sound/effects/rip1.ogg', 'sound/effects/rip2.ogg')
var/stop_bleeding = 1800
var/bleed_reduction = 0.02
var/lifespan = 150
self_delay = 20
max_amount = 12
grind_results = list(/datum/reagent/cellulose = 2)
Expand All @@ -152,13 +153,16 @@
amount = 12

/obj/item/stack/medical/gauze/heal(mob/living/target, mob/user)
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression
H.suppress_bloodloss(stop_bleeding)
to_chat(user, "<span class='notice'>You stop the bleeding of [target]!</span>")
if(iscarbon(target))
var/mob/living/carbon/C = target
var/obj/item/bodypart/BP = C.get_bodypart(check_zone(user.zone_selected))
if(!BP)
to_chat(user, span_warning("[C] doesn't have \a [parse_zone(user.zone_selected)]!"))
return
if(BP.can_bandage(user))
BP.apply_bandage(bleed_reduction, lifespan, name)
user.visible_message(span_notice("[user] wraps [C]'s [parse_zone(BP.body_zone)] with [src]."), span_notice("You wrap [C]'s [parse_zone(check_zone(user.zone_selected))] with [src]."), span_hear("You hear ruffling cloth."))
return TRUE
to_chat(user, "<span class='warning'>You can not use \the [src] on [target]!</span>")

/obj/item/stack/medical/gauze/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness())
Expand All @@ -178,8 +182,8 @@
/obj/item/stack/medical/gauze/improvised
name = "improvised gauze"
singular_name = "improvised gauze"
desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds."
stop_bleeding = 900
desc = "A roll of cloth roughly cut from something that can stop bleeding and slowly heal wounds."
bleed_reduction = 0.005

/obj/item/stack/medical/gauze/cyborg
custom_materials = null
Expand Down
24 changes: 10 additions & 14 deletions code/game/objects/items/stacks/tape.dm
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
grind_results = list(/datum/reagent/cellulose = 5)
usesound = 'sound/items/tape.ogg'

var/stop_bleed = 600
var/lifespan = 300
var/bleed_reduction = 0.002
var/nonorganic_heal = 5
var/self_delay = 30 //! Also used for the tapecuff delay
var/other_delay = 10
Expand Down Expand Up @@ -173,21 +174,17 @@
if(!affecting) //Missing limb?
to_chat(user, "<span class='warning'>[C] doesn't have \a [parse_zone(user.zone_selected)]!</span>")
return
if(!IS_ORGANIC_LIMB(affecting))
if(ishuman(C))
var/mob/living/carbon/human/H = C
if(!H.bleedsuppress && H.bleed_rate)
H.suppress_bloodloss(stop_bleed)
to_chat(user, "<span class='notice'>You tape up the bleeding of [C]!</span>")
return TRUE
to_chat(user, "<span class='warning'>[C] has a problem \the [src] won't fix!</span>")
else //Robotic patch-up
if(IS_ROBOTIC_LIMB(affecting)) //Robotic patch-up
if(affecting.brute_dam)
user.visible_message("<span class='notice'>[user] applies \the [src] on [C]'s [affecting.name].</span>", "<span class='green'>You apply \the [src] on [C]'s [affecting.name].</span>")
if(affecting.heal_damage(nonorganic_heal))
C.update_damage_overlays()
return TRUE
to_chat(user, "<span class='warning'>[src] can't patch what [C] has...</span>")
if(affecting.can_bandage(user))
affecting.apply_bandage(bleed_reduction, lifespan, name)
to_chat(user, "<span class='notice'>You tape up [C]'s [parse_zone(affecting.body_zone)]!</span>")
return TRUE
to_chat(user, "<span class='warning'>[src] can't patch what [C] has...</span>")

/obj/item/stack/tape/proc/apply_gag(mob/living/carbon/target, mob/user)
if(target.is_muzzled() || target.is_mouth_covered())
Expand Down Expand Up @@ -272,7 +269,7 @@
desc = "This roll of silver sorcery can fix just about anything."
icon_state = "tape_d"

stop_bleed = 800
lifespan = 400
nonorganic_heal = 20
prefix = "super sticky"
conferred_embed = EMBED_HARMLESS_SUPERIOR
Expand All @@ -297,7 +294,6 @@
desc = "Specialty insulated strips of adhesive plastic. Made for securing cables."
icon_state = "tape_e"

stop_bleed = 400
nonorganic_heal = 10
prefix = "insulated sticky"
siemens_coefficient = 0
Expand All @@ -321,6 +317,6 @@
desc = "Now THIS is engineering."
icon_state = "tape_y"

stop_bleed = 1000
lifespan = 500
nonorganic_heal = 30
prefix = "industry-standard sticky"
5 changes: 4 additions & 1 deletion code/game/objects/structures/petrified_statue.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@

if(petrified_mob)
petrified_mob.status_flags &= ~GODMODE
if(ishuman(petrified_mob))
var/mob/living/carbon/human/H = petrified_mob
H.bleedsuppress = FALSE
petrified_mob.forceMove(loc)
REMOVE_TRAIT(petrified_mob, TRAIT_MUTE, STATUE_MUTE)
petrified_mob.take_overall_damage((petrified_mob.health - obj_integrity + 100)) //any new damage the statue incurred is transfered to the mob
Expand All @@ -80,7 +83,7 @@
return 0
var/obj/structure/statue/petrified/S = new(loc, src, statue_timer)
S.name = "statue of [name]"
bleedsuppress = 1
bleedsuppress = TRUE
S.copy_overlays(src)
var/newcolor = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
S.add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY)
Expand Down
6 changes: 6 additions & 0 deletions code/modules/food_and_drinks/kitchen_machinery/microwave.dm
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,12 @@
playsound(src, 'sound/items/cig_light.ogg', 50, 1)
moveToNullspace()


/obj/item/ration_heater/get_temperature()
if(!uses)
return 0
. = ..()

/obj/item/ration_heater/proc/clear_cooking(datum/source)
SIGNAL_HANDLER
UnregisterSignal(tocook, COMSIG_PARENT_QDELETING)
Expand Down
35 changes: 9 additions & 26 deletions code/modules/mob/living/blood.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,6 @@
BLOOD SYSTEM
****************************************************/

/mob/living/carbon/human/proc/suppress_bloodloss(amount)
if(bleedsuppress)
return
else
bleedsuppress = TRUE
addtimer(CALLBACK(src, PROC_REF(resume_bleeding)), amount)

/mob/living/carbon/human/proc/resume_bleeding()
bleedsuppress = 0
if(stat != DEAD && bleed_rate)
to_chat(src, "<span class='warning'>The blood soaks through your bandage.</span>")


/mob/living/carbon/monkey/handle_blood()
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_HUSK))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
Expand All @@ -29,7 +16,6 @@
/mob/living/carbon/human/handle_blood()

if(NOBLOOD in dna.species.species_traits)
bleed_rate = 0
return

if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_HUSK))) //cryosleep or husked people do not pump the blood.
Expand Down Expand Up @@ -83,24 +69,20 @@
if(!HAS_TRAIT(src, TRAIT_NODEATH))
death()

var/temp_bleed = 0
//Bleeding out
var/limb_bleed = 0
for(var/obj/item/bodypart/BP as anything in bodyparts)
var/brutedamage = BP.brute_dam

if(BP.GetComponent(/datum/component/bandage))
continue
//We want an accurate reading of .len
listclearnulls(BP.embedded_objects)
for(var/obj/item/embeddies in BP.embedded_objects)
if(!embeddies.isEmbedHarmless())
temp_bleed += 0.5

if(brutedamage >= 20)
temp_bleed += (brutedamage * 0.013)

bleed_rate = max(bleed_rate - 0.5, temp_bleed)//if no wounds, other bleed effects (heparin) naturally decreases
BP.adjust_bleeding(0.1, BLOOD_LOSS_DAMAGE_MAXIMUM)
limb_bleed += BP.bleeding

if(bleed_rate && !bleedsuppress && !(HAS_TRAIT(src, TRAIT_FAKEDEATH)))
bleed(bleed_rate)
if(limb_bleed && !bleedsuppress && !HAS_TRAIT(src, TRAIT_FAKEDEATH))
bleed(limb_bleed)

//Makes a blood drop, leaking amt units of blood from the mob
/mob/living/carbon/proc/bleed(amt)
Expand All @@ -125,7 +107,8 @@

/mob/living/carbon/human/restore_blood()
blood_volume = BLOOD_VOLUME_NORMAL
bleed_rate = 0
for(var/obj/item/bodypart/BP as anything in get_bleeding_parts())
BP.bleeding = 0

/****************************************************
BLOOD TRANSFERS
Expand Down
Loading

0 comments on commit c9bf77d

Please sign in to comment.