mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-09 16:14:13 +00:00
* mob part bitflags in proper format, added tail part bitflag, hidetail clothing cover flag, reordered tail layer, added tail_underlimbs_layer for overlapping tails, limbs_layer for limbs placed under, tail_wagging + has_icon_skin_tone + tail_overlapped species anatomical flags, mutable_appearance() helper, made many suits cover tail, added tail wagging emote for species that can wag, made a species folder for species files, rewrote update_tail_showing(), character preview can show tails, vox tails are now separate from the body instead of being baked into the chest, vox tails will show slightly in north dir instead of being invisible * added color defines, undid bitfield format change, wag emote is shorter and doesnt show text on stopping wag, vox tails have their own file and have better names, removed icon manipulation from tail updating, species can have own tail icon, made vox tail north sprites full/complete sprites * wag emote no longer displays runechat, custom emotes can choose to not show runechat, restores old husk overlays * limb_tail tail define, moved 2 tail species flags to organ level, added span define, tail is on organ level, added support for cosmetic organs, tail preview icon shows better, added tail support to common surgeries * fix double tail organ, tail organ item only uses east sprite and is shifted to center it more * removes tail wagging * rename tail define, minor tweaks * more checks, fix char preview issues, remove unused proc, frankensteins spawn with random kind of tail, repaired tajaran and unathi tails credit to falcon2346, robotail support, rambler will not spawn with tail, gibbing drops tails * Update species.dm
514 lines
16 KiB
Plaintext
514 lines
16 KiB
Plaintext
//Updates the mob's health from organs and mob damage variables
|
|
/mob/living/carbon/human/updatehealth()
|
|
if(status_flags & GODMODE)
|
|
health = maxHealth
|
|
stat = CONSCIOUS
|
|
return
|
|
var/total_burn = 0
|
|
var/total_brute = 0
|
|
for(var/datum/organ/external/O in organs) //hardcoded to streamline things a bit
|
|
if(O.is_organic() && O.is_existing())
|
|
total_brute += O.brute_dam
|
|
total_burn += O.burn_dam
|
|
var/prevhealth = health
|
|
health = maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute
|
|
critlog(health,prevhealth)
|
|
if((maxHealth - total_burn) < config.health_threshold_dead)
|
|
death(FALSE)
|
|
ChangeToHusk()
|
|
return
|
|
|
|
/mob/living/carbon/human/getBrainLoss()
|
|
var/res = brainloss
|
|
if(species && species.has_organ["brain"])
|
|
var/datum/organ/internal/brain/sponge = internal_organs_by_name["brain"]
|
|
if(!sponge)
|
|
res += 200
|
|
else
|
|
if (sponge.is_bruised())
|
|
res += 20
|
|
if (sponge.is_broken())
|
|
res += 50
|
|
|
|
res = min(res,maxHealth*2)
|
|
return res
|
|
return 0
|
|
|
|
//These procs fetch a cumulative total damage from all organs
|
|
/mob/living/carbon/human/getBruteLoss(var/ignore_inorganic = FALSE)
|
|
var/amount = 0
|
|
for(var/datum/organ/external/O in organs)
|
|
if(ignore_inorganic && !O.is_organic())
|
|
continue
|
|
amount += O.brute_dam
|
|
return amount
|
|
|
|
/mob/living/carbon/human/getFireLoss(var/ignore_inorganic = FALSE)
|
|
var/amount = 0
|
|
for(var/datum/organ/external/O in organs)
|
|
if(ignore_inorganic && !O.is_organic())
|
|
continue
|
|
amount += O.burn_dam
|
|
return amount
|
|
|
|
|
|
/mob/living/carbon/human/adjustBruteLoss(var/amount)
|
|
|
|
amount = amount * brute_damage_modifier
|
|
|
|
if(INVOKE_EVENT(src, /event/damaged, "kind" = BRUTE, "amount" = amount))
|
|
return 0
|
|
|
|
if(amount > 0)
|
|
take_overall_damage(amount, 0)
|
|
else
|
|
heal_overall_damage(-amount, 0)
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
/mob/living/carbon/human/adjustFireLoss(var/amount)
|
|
amount = amount * burn_damage_modifier
|
|
|
|
if(INVOKE_EVENT(src, /event/damaged, "kind" = BURN, "amount" = amount))
|
|
return 0
|
|
|
|
if(amount > 0)
|
|
take_overall_damage(0, amount)
|
|
if(config.burn_damage_ash && amount >= config.burn_damage_ash)
|
|
dust(TRUE)
|
|
return
|
|
else
|
|
heal_overall_damage(0, -amount)
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
/mob/living/carbon/human/proc/adjustBruteLossByPart(var/amount, var/organ_name, var/obj/damage_source = null)
|
|
amount = amount * brute_damage_modifier
|
|
|
|
if(INVOKE_EVENT(src, /event/damaged, "kind" = BRUTE, "amount" = amount))
|
|
return 0
|
|
|
|
if (organ_name in organs_by_name)
|
|
var/datum/organ/external/O = get_organ(organ_name)
|
|
|
|
if(amount > 0)
|
|
O.take_damage(amount, 0, sharp=damage_source.is_sharp(), edge=damage_source.sharpness_flags & SHARP_BLADE, used_weapon=damage_source)
|
|
else
|
|
//if you don't want to heal robot organs, they you will have to check that yourself before using this proc.
|
|
O.heal_damage(-amount, 0, internal=0, robo_repair=(O.status & ORGAN_ROBOT))
|
|
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
/mob/living/carbon/human/proc/adjustFireLossByPart(var/amount, var/organ_name, var/obj/damage_source = null)
|
|
amount = amount * burn_damage_modifier
|
|
|
|
if(INVOKE_EVENT(src, /event/damaged, "kind" = BURN, "amount" = amount))
|
|
return 0
|
|
|
|
if (organ_name in organs_by_name)
|
|
var/datum/organ/external/O = get_organ(organ_name)
|
|
|
|
if(amount > 0)
|
|
O.take_damage(0, amount, sharp=damage_source.is_sharp(), edge=damage_source.sharpness_flags & SHARP_BLADE, used_weapon=damage_source)
|
|
else
|
|
//if you don't want to heal robot organs, they you will have to check that yourself before using this proc.
|
|
O.heal_damage(0, -amount, internal=0, robo_repair=(O.status & ORGAN_ROBOT))
|
|
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
/mob/living/carbon/human/Stun(amount)
|
|
if(M_HULK in mutations)
|
|
return
|
|
..()
|
|
|
|
/mob/living/carbon/human/Knockdown(amount)
|
|
if(M_HULK in mutations)
|
|
return
|
|
handle_spaghetti(100)
|
|
..()
|
|
|
|
/mob/living/carbon/human/Paralyse(amount)
|
|
if(M_HULK in mutations)
|
|
return
|
|
..()
|
|
|
|
/mob/living/carbon/human/adjustCloneLoss(var/amount)
|
|
..()
|
|
|
|
amount = amount * clone_damage_modifier
|
|
if(isslimeperson(src))
|
|
amount = 0
|
|
|
|
if(INVOKE_EVENT(src, /event/damaged, "kind" = CLONE, "amount" = amount))
|
|
return 0
|
|
|
|
var/heal_prob = max(0, 80 - getCloneLoss())
|
|
var/mut_prob = min(80, getCloneLoss()+10)
|
|
if (amount > 0)
|
|
if (prob(mut_prob))
|
|
var/list/datum/organ/external/candidates = list()
|
|
for (var/datum/organ/external/O in organs)
|
|
if(O.is_organic() && O.is_usable())
|
|
candidates |= O
|
|
if (candidates.len)
|
|
var/datum/organ/external/O = pick(candidates)
|
|
O.mutate()
|
|
to_chat(src, "<span class = 'notice'>Something is not right with your [O.display_name]...</span>")
|
|
return
|
|
else
|
|
if (prob(heal_prob))
|
|
for (var/datum/organ/external/O in organs)
|
|
if (O.is_existing() && O.status & ORGAN_MUTATED)
|
|
O.unmutate()
|
|
to_chat(src, "<span class = 'notice'>Your [O.display_name] is shaped normally again.</span>")
|
|
return
|
|
|
|
if (getCloneLoss() < 1)
|
|
for (var/datum/organ/external/O in organs)
|
|
if (O.is_existing() && O.status & ORGAN_MUTATED)
|
|
O.unmutate()
|
|
to_chat(src, "<span class = 'notice'>Your [O.display_name] is shaped normally again.</span>")
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
////////////////////////////////////////////
|
|
|
|
//Returns a list of damaged organs
|
|
/mob/living/carbon/human/proc/get_damaged_organs(var/brute, var/burn, var/ignore_inorganic = FALSE)
|
|
var/list/datum/organ/external/parts = list()
|
|
for(var/datum/organ/external/O in organs)
|
|
if(((brute && O.brute_dam) || (burn && O.burn_dam)) && !(ignore_inorganic && !O.is_organic()))
|
|
parts += O
|
|
return parts
|
|
|
|
//Returns a list of damageable organs
|
|
/mob/living/carbon/human/proc/get_damageable_organs(var/ignore_inorganics = FALSE)
|
|
var/list/datum/organ/external/parts = list()
|
|
for(var/datum/organ/external/O in organs)
|
|
if(!O.is_existing())
|
|
continue
|
|
if(ignore_inorganics && !O.is_organic())
|
|
continue
|
|
if(O.brute_dam + O.burn_dam < O.max_damage)
|
|
parts += O
|
|
return parts
|
|
|
|
//Heals ONE external organ, organ gets randomly selected from damaged ones.
|
|
//It automatically updates damage overlays if necesary
|
|
//It automatically updates health status
|
|
/mob/living/carbon/human/heal_organ_damage(var/brute, var/burn)
|
|
var/list/datum/organ/external/parts = get_damaged_organs(brute,burn,TRUE)
|
|
if(!parts.len)
|
|
return
|
|
var/datum/organ/external/picked = pick(parts)
|
|
if(picked.heal_damage(brute,burn))
|
|
UpdateDamageIcon()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
updatehealth()
|
|
|
|
|
|
/*
|
|
In most cases it makes more sense to use apply_damage() instead! And make sure to check armour if applicable.
|
|
*/
|
|
//Damages ONE external organ, organ gets randomly selected from damagable ones.
|
|
//It automatically updates damage overlays if necesary
|
|
//It automatically updates health status
|
|
/mob/living/carbon/human/take_organ_damage(var/brute, var/burn, var/sharp = 0, var/edge = 0, var/ignore_inorganics = FALSE)
|
|
var/list/datum/organ/external/parts = get_damageable_organs(ignore_inorganics)
|
|
if(!parts.len)
|
|
return
|
|
var/datum/organ/external/picked = pick(parts)
|
|
if(picked.take_damage(brute,burn,sharp,edge))
|
|
UpdateDamageIcon()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
updatehealth()
|
|
//speech_problem_flag = 1
|
|
|
|
|
|
//Heal MANY external organs, in random order
|
|
/mob/living/carbon/human/heal_overall_damage(var/brute, var/burn)
|
|
var/list/datum/organ/external/parts = get_damaged_organs(brute,burn,TRUE)
|
|
var/datum/organ/internal/heart/hivelord/H = get_heart()
|
|
if(istype(H)) // hivelord hearts just heal better
|
|
brute *= 2
|
|
burn *= 2
|
|
var/update = 0
|
|
while(parts.len && (brute>0 || burn>0) )
|
|
var/datum/organ/external/picked = pick(parts)
|
|
|
|
var/brute_was = picked.brute_dam
|
|
var/burn_was = picked.burn_dam
|
|
|
|
update |= picked.heal_damage(brute,burn)
|
|
|
|
brute -= (brute_was-picked.brute_dam)
|
|
burn -= (burn_was-picked.burn_dam)
|
|
|
|
parts -= picked
|
|
updatehealth()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
//speech_problem_flag = 1
|
|
if(update)
|
|
UpdateDamageIcon()
|
|
|
|
// damage MANY external organs, in random order
|
|
/mob/living/carbon/human/take_overall_damage(var/brute, var/burn, var/sharp = 0, var/edge = 0, var/used_weapon = null)
|
|
if(species && species.burn_mod)
|
|
burn = burn*species.burn_mod
|
|
if(species && species.brute_mod)
|
|
brute = brute*species.brute_mod
|
|
|
|
if(status_flags & GODMODE)
|
|
return 0 //godmode
|
|
|
|
. = brute + burn
|
|
|
|
var/list/datum/organ/external/parts = get_damageable_organs()
|
|
var/update = 0
|
|
while(parts.len && (brute>0 || burn>0) )
|
|
var/datum/organ/external/picked = pick(parts)
|
|
|
|
var/brute_was = picked.brute_dam
|
|
var/burn_was = picked.burn_dam
|
|
|
|
update |= picked.take_damage(brute,burn,sharp,edge,used_weapon)
|
|
brute -= (picked.brute_dam - brute_was)
|
|
burn -= (picked.burn_dam - burn_was)
|
|
|
|
parts -= picked
|
|
updatehealth()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
if(update)
|
|
UpdateDamageIcon()
|
|
|
|
////////////////////////////////////////////
|
|
|
|
/*
|
|
This function restores the subjects blood to max.
|
|
*/
|
|
/mob/living/carbon/human/proc/restore_blood()
|
|
if(!(species.anatomy_flags & NO_BLOOD))
|
|
var/blood_volume = vessel.get_reagent_amount(BLOOD)
|
|
vessel.add_reagent(BLOOD,560.0-blood_volume)
|
|
|
|
|
|
/*
|
|
This function restores all organs.
|
|
*/
|
|
/mob/living/carbon/human/restore_all_organs()
|
|
for(var/datum/organ/external/current_organ in organs)
|
|
current_organ.rejuvenate()
|
|
|
|
/mob/living/carbon/human/proc/HealDamage(zone, brute, burn)
|
|
var/datum/organ/external/E = get_organ(zone)
|
|
if(istype(E, /datum/organ/external))
|
|
if (E.heal_damage(brute, burn))
|
|
UpdateDamageIcon()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
else
|
|
return 0
|
|
return
|
|
|
|
|
|
/mob/living/carbon/human/get_organ(var/zone, cosmetic = FALSE)
|
|
RETURN_TYPE(/datum/organ/external)
|
|
if(!zone)
|
|
zone = LIMB_CHEST
|
|
if (zone in list( "eyes", "mouth" ))
|
|
zone = LIMB_HEAD
|
|
var/list/organ_list = organs_by_name.Copy()
|
|
if(cosmetic)
|
|
organ_list |= cosmetic_organs_by_name
|
|
return organ_list[zone]
|
|
|
|
/mob/living/carbon/human/proc/get_cosmetic_organ(zone)
|
|
RETURN_TYPE(/datum/organ/external)
|
|
return cosmetic_organs_by_name[zone]
|
|
|
|
//Picks a random usable organ from the organs passed to the arguments
|
|
//You can feed organ references, or organ strings into this obj
|
|
//So this is valid: pick_usable_organ(LIMB_LEFT_LEG, new /datum/organ/external/r_leg)
|
|
/mob/living/carbon/human/proc/pick_usable_organ()
|
|
var/list/organs = args.Copy()
|
|
|
|
ASSERT(organs.len) //this proc should always be called with arguments
|
|
|
|
//Convert list of strings to list of organ objects
|
|
for(var/organ_ in organs)
|
|
if(istext(organ_))
|
|
organs.Add(get_organ(organ_))
|
|
organs.Remove(organ_)
|
|
else if(!istype(organ_, /datum/organ/external))
|
|
organs.Remove(organ_)
|
|
|
|
var/datum/organ/external/result
|
|
|
|
while(!result && organs.len)
|
|
result = pick_n_take(organs)
|
|
if(!result.is_usable())
|
|
result = null
|
|
|
|
return result
|
|
|
|
//Proc that returns a list of organs converted from string IDs
|
|
//get_organs("l_leg", "r_leg") will return a list with left and right leg datums
|
|
//It will also accept lists with string IDs
|
|
/mob/living/carbon/human/get_organs()
|
|
var/list/organ_list = list()
|
|
|
|
for(var/O in args)
|
|
if(islist(O))
|
|
for(var/organ_id in O)
|
|
organ_list.Add(src.get_organ(organ_id))
|
|
|
|
else if(istext(O))
|
|
organ_list.Add(src.get_organ(O))
|
|
|
|
return organ_list
|
|
|
|
/mob/living/carbon/human/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/sharp = 0, var/edge = 0, var/obj/used_weapon = null, ignore_events = 0)
|
|
|
|
//visible_message("Hit debug. [damage] | [damagetype] | [def_zone] | [blocked] | [sharp] | [used_weapon]")
|
|
if((damagetype != BRUTE) && (damagetype != BURN))
|
|
return ..(damage, damagetype, def_zone, blocked, ignore_events = ignore_events)
|
|
if(blocked >= 100)
|
|
return 0
|
|
|
|
var/datum/organ/external/organ = null
|
|
if(isorgan(def_zone))
|
|
organ = def_zone
|
|
else
|
|
if(!def_zone)
|
|
def_zone = ran_zone(def_zone)
|
|
organ = get_organ(check_zone(def_zone))
|
|
if(!organ)
|
|
return 0
|
|
|
|
if(blocked)
|
|
damage = (damage/100)*(100-blocked)
|
|
|
|
if(!ignore_events && INVOKE_EVENT(src, /event/damaged, "kind" = damagetype, "amount" = damage))
|
|
return 0 //This event code is also in the mob/living parent which this proc mostly overrides.
|
|
|
|
switch(damagetype)
|
|
if(BRUTE)
|
|
damage = damage * brute_damage_modifier
|
|
|
|
if (damage > 0)
|
|
damageoverlaytemp = 20
|
|
|
|
if(organ.take_damage(damage, 0, sharp, edge, used_weapon))
|
|
UpdateDamageIcon(1)
|
|
if(BURN)
|
|
damage = damage * burn_damage_modifier
|
|
|
|
if (damage > 0)
|
|
damageoverlaytemp = 20
|
|
|
|
if(organ.take_damage(0, damage, sharp, edge, used_weapon))
|
|
UpdateDamageIcon(1)
|
|
|
|
// Will set our damageoverlay icon to the next level, which will then be set back to the normal level the next mob.Life().
|
|
updatehealth()
|
|
hud_updateflag |= 1 << HEALTH_HUD
|
|
|
|
//Embedded projectile code.
|
|
if(!organ)
|
|
return damage
|
|
/*/vg/ EDIT
|
|
if(istype(used_weapon,/obj/item/weapon))
|
|
var/obj/item/weapon/W = used_weapon //Sharp objects will always embed if they do enough damage.
|
|
if( (damage > (10*W.w_class)) && ( (sharp && !ismob(W.loc)) || prob(damage/W.w_class) ) )
|
|
if(!istype(W, /obj/item/weapon/kitchen/utensil/knife/large/butch/meatcleaver))
|
|
organ.implants += W
|
|
visible_message("<span class='danger'>\The [W] sticks in the wound!</span>")
|
|
W.add_blood(src)
|
|
if(ismob(W.loc))
|
|
var/mob/living/H = W.loc
|
|
H.drop_item(W, src)
|
|
W.forceMove(src)
|
|
*/
|
|
if(istype(used_weapon,/obj/item/projectile/bullet)) //We don't want to use the actual projectile item, so we spawn some shrapnel.
|
|
var/obj/item/projectile/bullet/P = used_weapon
|
|
if(prob(75) && damagetype == BRUTE && P.embed)
|
|
var/obj/item/weapon/shard/shrapnel/S = new()
|
|
S.name = "[P.name] shrapnel"
|
|
S.desc = "[S.desc] It looks like it was fired from [P.shot_from]."
|
|
S.forceMove(src)
|
|
organ.implants += S
|
|
if(P.embed_message)
|
|
visible_message("<span class='danger'>The projectile sticks in the wound!</span>")
|
|
S.add_blood(src)
|
|
if(istype(used_weapon,/obj/item/projectile/flare)) //We want them to carry the flare, not a projectile
|
|
var/obj/item/projectile/flare/F = used_weapon
|
|
if(damagetype == BURN && F.embed && (istype(F.shot_from, /obj/item/weapon/gun/projectile/flare/syndicate) || istype(F.shot_from, /obj/item/weapon/gun/lawgiver)) && prob(75)) //only syndicate guns are dangerous, except for the lawgiver, which is intended to fire incendiary rounds
|
|
var/obj/item/device/flashlight/flare/FS = new
|
|
FS.name = "shot [FS.name]"
|
|
FS.desc = "[FS.desc]. It looks like it was fired from [F.shot_from]."
|
|
FS.forceMove(src)
|
|
organ.implants += FS
|
|
visible_message("<span class='danger'>The flare sticks in the wound!</span>")
|
|
FS.add_blood(src)
|
|
FS.luminosity = 4 //not so bright, because it's inside them
|
|
FS.Light(src) //Now they glow, because the flare is lit
|
|
if(prob(80)) //tends to happen, which is good
|
|
visible_message("<span class='danger'><b>[name]</b> bursts into flames!</span>", "<span class='danger'>You burst into flames!</span>")
|
|
on_fire = 1
|
|
adjust_fire_stacks(0.5) //as seen in ignite code
|
|
update_icon = 1
|
|
qdel(F)
|
|
return damage
|
|
|
|
//Adds cancer, including stage of cancer and limb
|
|
//Right now cancer is adminbus only. You can inflict it via the full (old) Player Panel and all "prayer types" (includes Centcomm message)
|
|
//Of course, should it ever come back for realsies, that's the right way to do it. But let's not be silly now
|
|
//IMPORTANT NOTE: Currently only works on external organs, because the person who wrote organ code has brain cancer, hopefully I will sweep back to fix this in a later PR
|
|
//Since I'd have to change hundreds of procs going through organs, that's not something I'll do now
|
|
/mob/living/carbon/human/proc/add_cancer(var/stage = 1, var/target)
|
|
|
|
var/datum/organ/picked_organ
|
|
if(target)
|
|
picked_organ = organs_by_name["[target]"]
|
|
else
|
|
picked_organ = pick(organs)
|
|
|
|
if(picked_organ)
|
|
picked_organ.cancer_stage += stage //This can pick a limb which already has cancer, in which case it will add to it
|
|
|
|
/mob/living/carbon/human/proc/limitedrevive()
|
|
resurrect()
|
|
timeofdeath = 0
|
|
tod = null
|
|
|
|
toxloss = 0
|
|
oxyloss = 0
|
|
bruteloss = 0
|
|
fireloss = 0
|
|
for(var/datum/organ/external/O in organs)
|
|
if(O.destspawn || O.is_robotic())
|
|
continue
|
|
O.rejuvenate()
|
|
O.number_wounds = 0
|
|
O.wounds = list()
|
|
heal_overall_damage(1000, 1000)
|
|
if(reagents)
|
|
reagents.clear_reagents()
|
|
restore_blood()
|
|
bodytemperature = 310
|
|
stat = status_flags & BUDDHAMODE ? CONSCIOUS : UNCONSCIOUS
|
|
regenerate_icons()
|
|
flash_eyes(visual = 1)
|
|
apply_effect(10, EYE_BLUR)
|
|
apply_effect(10, WEAKEN)
|
|
update_canmove()
|
|
|
|
/mob/living/carbon/human/apply_radiation(var/rads, var/application = RAD_EXTERNAL)
|
|
if(species.flags & RAD_IMMUNE)
|
|
return
|
|
if(application == RAD_EXTERNAL)
|
|
INVOKE_EVENT(src, /event/irradiate, "user" = src, "rads" = rads)
|
|
if(reagents)
|
|
if(reagents.has_reagent(LITHOTORCRAZINE))
|
|
rads /= 2
|
|
if(species.rad_mod)
|
|
rads *= species.rad_mod
|
|
return ..()
|
|
|