Files
+37 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

382 lines
13 KiB
Plaintext

/mob/living/carbon/apply_damage(
damage = 0,
damagetype = BRUTE,
def_zone = null,
blocked = 0,
forced = FALSE,
spread_damage = FALSE,
wound_bonus = 0,
exposed_wound_bonus = 0,
sharpness = NONE,
attack_direction = null,
attacking_item,
wound_clothing = TRUE,
)
// Spread damage should always have def zone be null
if(spread_damage)
def_zone = null
// Otherwise if def zone is null, we'll get a random bodypart / zone to hit.
// ALso we'll automatically covnert string def zones into bodyparts to pass into parent call.
else if(!isbodypart(def_zone))
var/random_zone = check_zone(def_zone || get_random_valid_zone(def_zone))
def_zone = get_bodypart(random_zone) || get_bodypart()
. = ..()
// Taking brute or burn to bodyparts gives a damage flash
if(def_zone && (damagetype == BRUTE || damagetype == BURN))
damageoverlaytemp += .
return .
/mob/living/carbon/human/get_damage_mod(damage_type)
if (!dna?.species?.damage_modifier)
return ..()
var/species_mod = (100 - dna.species.damage_modifier) / 100
return ..() * species_mod
/mob/living/carbon/human/apply_damage(
damage = 0,
damagetype = BRUTE,
def_zone = null,
blocked = 0,
forced = FALSE,
spread_damage = FALSE,
wound_bonus = 0,
exposed_wound_bonus = 0,
sharpness = NONE,
attack_direction = null,
attacking_item,
wound_clothing = TRUE,
)
// Add relevant DR modifiers into blocked value to pass to parent
blocked += physiology?.damage_resistance
blocked += dna?.species?.damage_modifier
return ..()
/mob/living/carbon/human/get_incoming_damage_modifier(
damage = 0,
damagetype = BRUTE,
def_zone = null,
sharpness = NONE,
attack_direction = null,
attacking_item,
)
var/final_mod = ..()
switch(damagetype)
if(BRUTE)
final_mod *= physiology.brute_mod
if(BURN)
final_mod *= physiology.burn_mod
if(TOX)
final_mod *= physiology.tox_mod
if(OXY)
final_mod *= physiology.oxy_mod
if(STAMINA)
final_mod *= physiology.stamina_mod
if(BRAIN)
final_mod *= physiology.brain_mod
return final_mod
//These procs fetch a cumulative total damage from all bodyparts
/mob/living/carbon/get_brute_loss()
var/amount = 0
for(var/obj/item/bodypart/bodypart as anything in get_bodyparts())
amount += bodypart.brute_dam
return round(amount, DAMAGE_PRECISION)
/mob/living/carbon/get_fire_loss()
var/amount = 0
for(var/obj/item/bodypart/bodypart as anything in get_bodyparts())
amount += bodypart.burn_dam
return round(amount, DAMAGE_PRECISION)
/**
* Returns the amount of bruteloss across all bodyparts meeting the matching bodytype.
* Useful for if you would like to check the bruteloss for only organic bodyparts, for example.
*
* Arguments:
* * required_bodytype - The bodytype(s) to match against.
*/
/mob/living/carbon/proc/get_brute_loss_for_type(required_bodytype = ALL)
var/amount = 0
for(var/obj/item/bodypart/bodypart as anything in get_bodyparts())
if(!(bodypart.bodytype & required_bodytype))
continue
amount += bodypart.brute_dam
return round(amount, DAMAGE_PRECISION)
/**
* Returns the amount of fireloss across all bodyparts meeting the matching bodytype.
* Useful for if you would like to check the fireloss for only organic bodyparts, for example.
*
* Arguments:
* * required_bodytype - The bodytype(s) to match against.
*/
/mob/living/carbon/proc/get_fire_loss_for_type(required_bodytype = ALL)
var/amount = 0
for(var/obj/item/bodypart/bodypart as anything in get_bodyparts())
if(!(bodypart.bodytype & required_bodytype))
continue
amount += bodypart.burn_dam
return round(amount, DAMAGE_PRECISION)
/mob/living/carbon/adjust_brute_loss(amount, updating_health = TRUE, forced = FALSE, required_bodytype)
if(!can_adjust_brute_loss(amount, forced, required_bodytype))
return 0
if(amount > 0)
. = take_overall_damage(brute = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype)
else
. = heal_overall_damage(brute = abs(amount), required_bodytype = required_bodytype, updating_health = updating_health, forced = forced)
/mob/living/carbon/set_brute_loss(amount, updating_health = TRUE, forced = FALSE, required_bodytype)
if(!forced && HAS_TRAIT(src, TRAIT_GODMODE))
return FALSE
var/current = get_brute_loss()
var/diff = amount - current
if(!diff)
return FALSE
return adjust_brute_loss(diff, updating_health, forced, required_bodytype)
/mob/living/carbon/adjust_fire_loss(amount, updating_health = TRUE, forced = FALSE, required_bodytype)
if(!can_adjust_fire_loss(amount, forced, required_bodytype))
return 0
if(amount > 0)
. = take_overall_damage(burn = amount, updating_health = updating_health, forced = forced, required_bodytype = required_bodytype)
else
. = heal_overall_damage(burn = abs(amount), required_bodytype = required_bodytype, updating_health = updating_health, forced = forced)
/mob/living/carbon/set_fire_loss(amount, updating_health = TRUE, forced = FALSE, required_bodytype)
if(!forced && HAS_TRAIT(src, TRAIT_GODMODE))
return FALSE
var/current = get_fire_loss()
var/diff = amount - current
if(!diff)
return FALSE
return adjust_fire_loss(diff, updating_health, forced, required_bodytype)
/mob/living/carbon/human/adjust_tox_loss(amount, updating_health = TRUE, forced = FALSE, required_biotype = ALL)
. = ..()
if(. >= 0) // 0 = no damage, + values = healed damage
return .
if(AT_TOXIN_VOMIT_THRESHOLD(src))
apply_status_effect(/datum/status_effect/tox_vomit)
/mob/living/carbon/human/set_tox_loss(amount, updating_health, forced, required_biotype)
. = ..()
if(. >= 0)
return .
if(AT_TOXIN_VOMIT_THRESHOLD(src))
apply_status_effect(/datum/status_effect/tox_vomit)
/mob/living/carbon/received_stamina_damage(current_level, amount_actual, amount)
. = ..()
if((maxHealth - current_level) <= crit_threshold && stat != DEAD)
apply_status_effect(/datum/status_effect/incapacitating/stamcrit)
/**
* If an organ exists in the slot requested, and we are capable of taking damage (we don't have TRAIT_GODMODE), call the damage proc on that organ.
*
* Arguments:
* * slot - organ slot, like [ORGAN_SLOT_HEART]
* * amount - damage to be done
* * maximum - currently an arbitrarily large number, can be set so as to limit damage
* * required_organ_flag - targets only a specific organ type if set to ORGAN_ORGANIC or ORGAN_ROBOTIC
*
* Returns: The net change in damage from apply_organ_damage()
*/
/mob/living/carbon/adjust_organ_loss(slot, amount, maximum, required_organ_flag = NONE)
var/obj/item/organ/affected_organ = get_organ_slot(slot)
if(!affected_organ || HAS_TRAIT(src, TRAIT_GODMODE))
return FALSE
if(required_organ_flag && !(affected_organ.organ_flags & required_organ_flag))
return FALSE
return affected_organ.apply_organ_damage(amount, maximum)
/**
* If an organ exists in the slot requested, and we are capable of taking damage (we don't have TRAIT_GODMODE), call the set damage proc on that organ, which can
* set or clear the failing variable on that organ, making it either cease or start functions again, unlike adjust_organ_loss.
*
* Arguments:
* * slot - organ slot, like [ORGAN_SLOT_HEART]
* * amount - damage to be set to
* * required_organ_flag - targets only a specific organ type if set to ORGAN_ORGANIC or ORGAN_ROBOTIC
*
* Returns: The net change in damage from set_organ_damage()
*/
/mob/living/carbon/set_organ_loss(slot, amount, required_organ_flag = NONE)
var/obj/item/organ/affected_organ = get_organ_slot(slot)
if(!affected_organ || HAS_TRAIT(src, TRAIT_GODMODE))
return FALSE
if(required_organ_flag && !(affected_organ.organ_flags & required_organ_flag))
return FALSE
if(affected_organ.damage == amount)
return FALSE
return affected_organ.set_organ_damage(amount)
/**
* If an organ exists in the slot requested, return the amount of damage that organ has
*
* Arguments:
* * slot - organ slot, like [ORGAN_SLOT_HEART]
* * required_organ_flag - if you only want to check the damage of organs with the specified organ_flag(s) then you can use this.
*/
/mob/living/carbon/get_organ_loss(slot, required_organ_flag = NONE)
var/obj/item/organ/affected_organ = get_organ_slot(slot)
if(affected_organ)
if(required_organ_flag && !(affected_organ.organ_flags & required_organ_flag))
return
return affected_organ.damage
////////////////////////////////////////////
///Returns a list of damaged bodyparts
/mob/living/carbon/proc/get_damaged_bodyparts(brute = FALSE, burn = FALSE, required_bodytype = NONE, target_zone = null)
var/list/obj/item/bodypart/parts = list()
for(var/obj/item/bodypart/BP as anything in get_bodyparts())
if(required_bodytype && !(BP.bodytype & required_bodytype))
continue
if(!isnull(target_zone) && BP.body_zone != target_zone)
continue
if((brute && BP.brute_dam) || (burn && BP.burn_dam))
parts += BP
return parts
///Returns a list of damageable bodyparts
/mob/living/carbon/proc/get_damageable_bodyparts(required_bodytype)
var/list/obj/item/bodypart/parts = list()
for(var/obj/item/bodypart/BP as anything in get_bodyparts())
if(required_bodytype && !(BP.bodytype & required_bodytype))
continue
if(BP.brute_dam + BP.burn_dam < BP.max_damage)
parts += BP
return parts
///Returns a list of bodyparts with wounds (in case someone has a wound on an otherwise fully healed limb)
/mob/living/carbon/proc/get_wounded_bodyparts(required_bodytype)
var/list/obj/item/bodypart/parts = list()
for(var/obj/item/bodypart/BP as anything in get_bodyparts())
if(required_bodytype && !(BP.bodytype & required_bodytype))
continue
if(LAZYLEN(BP.wounds))
parts += BP
return parts
/**
* Heals ONE bodypart randomly selected from damaged ones.
* It automatically updates damage overlays if necessary
*
* It automatically updates health status
*/
/mob/living/carbon/heal_bodypart_damage(brute = 0, burn = 0, updating_health = TRUE, required_bodytype = NONE, target_zone = null)
. = FALSE
var/list/obj/item/bodypart/parts = get_damaged_bodyparts(brute, burn, required_bodytype, target_zone)
if(!parts.len)
return
var/obj/item/bodypart/picked = pick(parts)
var/damage_calculator = picked.get_damage() //heal_damage returns update status T/F instead of amount healed so we dance gracefully around this
if(picked.heal_damage(abs(brute), abs(burn), required_bodytype = required_bodytype))
update_damage_overlays()
return (damage_calculator - picked.get_damage())
/**
* Damages ONE bodypart randomly selected from damagable ones.
*
* It automatically updates damage overlays if necessary
*
* It automatically updates health status
*/
/mob/living/carbon/take_bodypart_damage(brute = 0, burn = 0, updating_health = TRUE, required_bodytype, check_armor = FALSE, wound_bonus = 0, exposed_wound_bonus = 0, sharpness = NONE)
. = FALSE
if(HAS_TRAIT(src, TRAIT_GODMODE))
return
var/list/obj/item/bodypart/parts = get_damageable_bodyparts(required_bodytype)
if(!parts.len)
return
var/obj/item/bodypart/picked = pick(parts)
var/damage_calculator = picked.get_damage()
if(picked.receive_damage(abs(brute), abs(burn), check_armor ? run_armor_check(picked, (brute ? MELEE : burn ? FIRE : null)) : FALSE, wound_bonus = wound_bonus, exposed_wound_bonus = exposed_wound_bonus, sharpness = sharpness))
update_damage_overlays()
return (damage_calculator - picked.get_damage())
/mob/living/carbon/heal_overall_damage(brute = 0, burn = 0, stamina = 0, required_bodytype, updating_health = TRUE, forced = FALSE)
. = FALSE
// treat negative args as positive
brute = abs(brute)
burn = abs(burn)
var/list/obj/item/bodypart/parts = get_damaged_bodyparts(brute, burn, required_bodytype)
var/update = NONE
while(parts.len && (brute > 0 || burn > 0))
var/obj/item/bodypart/picked = pick(parts)
var/brute_was = picked.brute_dam
var/burn_was = picked.burn_dam
. += picked.get_damage()
update |= picked.heal_damage(brute, burn, updating_health = FALSE, forced = forced, required_bodytype = required_bodytype)
. -= picked.get_damage() // return the net amount of damage healed
brute = round(brute - (brute_was - picked.brute_dam), DAMAGE_PRECISION)
burn = round(burn - (burn_was - picked.burn_dam), DAMAGE_PRECISION)
parts -= picked
if(!.) // no change? no need to update anything
return
if(updating_health)
updatehealth()
if(update)
update_damage_overlays()
/mob/living/carbon/take_overall_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, forced = FALSE, required_bodytype)
. = FALSE
if(!forced && HAS_TRAIT(src, TRAIT_GODMODE))
return
// treat negative args as positive
brute = abs(brute)
burn = abs(burn)
var/list/obj/item/bodypart/parts = get_damageable_bodyparts(required_bodytype)
var/update = NONE
while(parts.len && (brute > 0 || burn > 0))
var/obj/item/bodypart/picked = pick(parts)
var/brute_per_part = round(brute/parts.len, DAMAGE_PRECISION)
var/burn_per_part = round(burn/parts.len, DAMAGE_PRECISION)
var/brute_was = picked.brute_dam
var/burn_was = picked.burn_dam
. += picked.get_damage()
// disabling wounds from these for now cuz your entire body snapping cause your heart stopped would suck
update |= picked.receive_damage(brute_per_part, burn_per_part, blocked = FALSE, updating_health = FALSE, forced = forced, required_bodytype = required_bodytype, wound_bonus = CANT_WOUND)
. -= picked.get_damage() // return the net amount of damage healed
brute = round(brute - (picked.brute_dam - brute_was), DAMAGE_PRECISION)
burn = round(burn - (picked.burn_dam - burn_was), DAMAGE_PRECISION)
parts -= picked
if(!.) // no change? no need to update anything
return
if(updating_health)
updatehealth()
if(update)
update_damage_overlays()