mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 01:34:01 +00:00
## About The Pull Request
Port of my changes from [Monkestation/Monkestation2.0@`c54fc87`
(#7295)](c54fc872bf)
This nukes `/obj/item/bodypart/proc/get_modified_bleed_rate()`, instead
everything just uses the `cache_bleed_rate` var - `refresh_bleed_rate()`
will now take into account body position and grasping in the same way
`get_modified_bleed_rate` did.
(Un)grasping a limb already called `refresh_bleed_rate()` anyways, so
the only other change needed was making
`COMSIG_LIVING_SET_BODY_POSITION` also call it.
1386 lines
46 KiB
Plaintext
1386 lines
46 KiB
Plaintext
/mob/living/carbon/Initialize(mapload)
|
|
. = ..()
|
|
create_carbon_reagents()
|
|
update_body(is_creating = TRUE) //to update the carbon's new bodyparts appearance
|
|
living_flags &= ~STOP_OVERLAY_UPDATE_BODY_PARTS
|
|
|
|
register_context()
|
|
|
|
GLOB.carbon_list += src
|
|
ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead
|
|
ADD_TRAIT(src, TRAIT_CAN_THROW_ITEMS, INNATE_TRAIT) // same here
|
|
breathing_loop = new(src, _direct = TRUE)
|
|
|
|
/mob/living/carbon/Destroy()
|
|
//This must be done first, so the mob ghosts correctly before DNA etc is nulled
|
|
. = ..()
|
|
|
|
living_flags |= STOP_OVERLAY_UPDATE_BODY_PARTS
|
|
|
|
QDEL_LIST(hand_bodyparts)
|
|
QDEL_LIST(organs)
|
|
QDEL_LIST(bodyparts)
|
|
QDEL_LIST(implants)
|
|
for(var/wound in all_wounds) // these LAZYREMOVE themselves when deleted so no need to remove the list here
|
|
qdel(wound)
|
|
for(var/scar in all_scars)
|
|
qdel(scar)
|
|
remove_from_all_data_huds()
|
|
QDEL_NULL(dna)
|
|
QDEL_NULL(breathing_loop)
|
|
GLOB.carbon_list -= src
|
|
|
|
/mob/living/carbon/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
. = ..()
|
|
if(. & ITEM_INTERACT_ANY_BLOCKER)
|
|
return .
|
|
// Needs to happen after parent call otherwise wounds are prioritized over surgery
|
|
for(var/datum/wound/wound as anything in shuffle(all_wounds))
|
|
if(wound.try_treating(tool, user))
|
|
return ITEM_INTERACT_SUCCESS
|
|
return .
|
|
|
|
/mob/living/carbon/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
|
. = ..()
|
|
var/hurt = TRUE
|
|
var/extra_speed = 0
|
|
var/oof_noise = FALSE //We smacked something with denisty, so play a noise
|
|
var/mob/thrower = throwingdatum?.get_thrower()
|
|
if(thrower != src)
|
|
extra_speed = min(max(0, throwingdatum.speed - initial(throw_speed)), CARBON_MAX_IMPACT_SPEED_BONUS)
|
|
|
|
if(istype(throwingdatum))
|
|
hurt = !throwingdatum.gentle
|
|
if(hurt && hit_atom.density)
|
|
if(isturf(hit_atom))
|
|
Paralyze(2 SECONDS)
|
|
take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
else if(isstructure(hit_atom) && extra_speed)
|
|
Paralyze(1 SECONDS)
|
|
take_bodypart_damage(5 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
else if(!iscarbon(hit_atom) && extra_speed)
|
|
take_bodypart_damage(5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
visible_message(span_danger("[src] crashes into [hit_atom][extra_speed ? " really hard" : ""]!"),\
|
|
span_userdanger("You violently crash into [hit_atom][extra_speed ? " extra hard" : ""]!"))
|
|
log_combat(hit_atom, src, "crashes ")
|
|
oof_noise = TRUE
|
|
|
|
if(iscarbon(hit_atom) && hit_atom != src)
|
|
var/mob/living/carbon/victim = hit_atom
|
|
var/blocked = FALSE
|
|
if(victim.movement_type & FLYING)
|
|
return
|
|
if(!hurt)
|
|
return
|
|
|
|
if(. == SUCCESSFUL_BLOCK || victim.check_block(src, 0, "[name]", LEAP_ATTACK))
|
|
blocked = TRUE
|
|
|
|
take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
Paralyze(2 SECONDS)
|
|
oof_noise = TRUE
|
|
|
|
if(blocked)
|
|
visible_message(span_danger("[src] crashes into [victim][extra_speed ? " really hard" : ""], but [victim] blocked the worst of it!"),\
|
|
span_userdanger("You violently crash into [victim][extra_speed ? " extra hard" : ""], but [victim] managed to block the worst of it!"))
|
|
log_combat(src, victim, "crashed into and was blocked by")
|
|
return
|
|
else if(HAS_TRAIT(victim, TRAIT_BRAWLING_KNOCKDOWN_BLOCKED))
|
|
victim.take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
victim.apply_damage(10 + 10 * extra_speed, STAMINA)
|
|
victim.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS)
|
|
visible_message(span_danger("[src] crashes into [victim][extra_speed ? " really hard" : ""], but [victim] was able to stay on their feet!"),\
|
|
span_userdanger("You violently crash into [victim][extra_speed ? " extra hard" : ""], but [victim] managed to stay on their feet!"))
|
|
else
|
|
victim.Paralyze(2 SECONDS)
|
|
victim.take_bodypart_damage(10 + 5 * extra_speed, check_armor = TRUE, wound_bonus = extra_speed * 5)
|
|
visible_message(span_danger("[src] crashes into [victim][extra_speed ? " really hard" : ""], knocking them both over!"),\
|
|
span_userdanger("You violently crash into [victim][extra_speed ? " extra hard" : ""]!"))
|
|
log_combat(src, victim, "crashed into")
|
|
|
|
if(oof_noise)
|
|
playsound(src,'sound/items/weapons/punch1.ogg',50,TRUE)
|
|
|
|
/mob/living/carbon/proc/canBeHandcuffed()
|
|
return FALSE
|
|
|
|
/mob/living/carbon/proc/create_carbon_reagents()
|
|
if (!isnull(reagents))
|
|
return
|
|
|
|
create_reagents(1000, REAGENT_HOLDER_ALIVE)
|
|
|
|
/mob/living/carbon/Topic(href, href_list)
|
|
..()
|
|
if(href_list["embedded_object"])
|
|
var/obj/item/bodypart/limb = locate(href_list["embedded_limb"]) in bodyparts
|
|
if(!limb)
|
|
return
|
|
var/obj/item/weapon = locate(href_list["embedded_object"]) in limb.embedded_objects
|
|
if(!weapon || weapon.loc != src) //no item, no limb, or item is not in limb or in the person anymore
|
|
return
|
|
weapon.get_embed().rip_out(usr)
|
|
return
|
|
|
|
if(href_list["show_paper_note"])
|
|
var/obj/item/paper/paper_note = locate(href_list["show_paper_note"])
|
|
if(!paper_note)
|
|
return
|
|
|
|
paper_note.show_through_camera(usr)
|
|
|
|
/mob/living/carbon/on_fall()
|
|
. = ..()
|
|
loc?.handle_fall(src) //it's loc so it doesn't call the mob's handle_fall which does nothing
|
|
|
|
/mob/living/carbon/resist_buckle()
|
|
if(!HAS_TRAIT(src, TRAIT_RESTRAINED))
|
|
buckled.user_unbuckle_mob(src, src)
|
|
return
|
|
|
|
changeNext_move(CLICK_CD_BREAKOUT)
|
|
last_special = world.time + CLICK_CD_BREAKOUT
|
|
var/buckle_cd = 1 MINUTES
|
|
|
|
if(handcuffed)
|
|
var/obj/item/restraints/cuffs = src.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
|
|
buckle_cd = cuffs.breakouttime
|
|
|
|
visible_message(span_warning("[src] attempts to unbuckle [p_them()]self!"),
|
|
span_notice("You attempt to unbuckle yourself... \
|
|
(This will take around [DisplayTimeText(buckle_cd)] and you must stay still.)"))
|
|
|
|
if(!do_after(src, buckle_cd, target = src, timed_action_flags = IGNORE_HELD_ITEM, hidden = TRUE))
|
|
if(buckled)
|
|
to_chat(src, span_warning("You fail to unbuckle yourself!"))
|
|
return
|
|
|
|
if(QDELETED(src) || isnull(buckled))
|
|
return
|
|
|
|
buckled.user_unbuckle_mob(src, src)
|
|
|
|
|
|
/mob/living/carbon/resist_fire()
|
|
return !!apply_status_effect(/datum/status_effect/stop_drop_roll)
|
|
|
|
/mob/living/carbon/resist_restraints()
|
|
var/obj/item/I = null
|
|
var/type = 0
|
|
if(handcuffed)
|
|
I = handcuffed
|
|
type = 1
|
|
else if(legcuffed)
|
|
I = legcuffed
|
|
type = 2
|
|
if(I)
|
|
if(type == 1)
|
|
changeNext_move(I.resist_cooldown)
|
|
last_special = world.time + I.resist_cooldown
|
|
if(type == 2)
|
|
changeNext_move(CLICK_CD_RANGE)
|
|
last_special = world.time + CLICK_CD_RANGE
|
|
cuff_resist(I)
|
|
|
|
|
|
/**
|
|
* Helper to break the cuffs from hands
|
|
* @param {obj/item} cuffs - The cuffs to break
|
|
* @param {number} breakouttime - The time it takes to break the cuffs. Use SECONDS/MINUTES defines
|
|
* @param {number} cuff_break - Speed multiplier, 0 is default, see _DEFINES\combat.dm
|
|
*/
|
|
/mob/living/carbon/proc/cuff_resist(obj/item/cuffs, breakouttime = 1 MINUTES, cuff_break = 0)
|
|
if((cuff_break != INSTANT_CUFFBREAK) && (SEND_SIGNAL(src, COMSIG_MOB_REMOVING_CUFFS, cuffs) & COMSIG_MOB_BLOCK_CUFF_REMOVAL))
|
|
return //The blocking object should sent a fluff-appropriate to_chat about cuff removal being blocked
|
|
if(cuffs.item_flags & BEING_REMOVED)
|
|
to_chat(src, span_warning("You're already attempting to remove [cuffs]!"))
|
|
return
|
|
cuffs.item_flags |= BEING_REMOVED
|
|
breakouttime = cuffs.breakouttime
|
|
if(!cuff_break)
|
|
visible_message(span_warning("[src] attempts to remove [cuffs]!"))
|
|
to_chat(src, span_notice("You attempt to remove [cuffs]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)"))
|
|
if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM, hidden = TRUE))
|
|
. = clear_cuffs(cuffs, cuff_break)
|
|
else
|
|
to_chat(src, span_warning("You fail to remove [cuffs]!"))
|
|
|
|
else if(cuff_break == FAST_CUFFBREAK)
|
|
breakouttime = 5 SECONDS
|
|
visible_message(span_warning("[src] is trying to break [cuffs]!"))
|
|
to_chat(src, span_notice("You attempt to break [cuffs]... (This will take around 5 seconds and you need to stand still.)"))
|
|
if(do_after(src, breakouttime, target = src, timed_action_flags = IGNORE_HELD_ITEM))
|
|
. = clear_cuffs(cuffs, cuff_break)
|
|
else
|
|
to_chat(src, span_warning("You fail to break [cuffs]!"))
|
|
|
|
else if(cuff_break == INSTANT_CUFFBREAK)
|
|
. = clear_cuffs(cuffs, cuff_break)
|
|
cuffs.item_flags &= ~BEING_REMOVED
|
|
|
|
/mob/living/carbon/proc/uncuff()
|
|
if (handcuffed)
|
|
var/obj/item/W = handcuffed
|
|
set_handcuffed(null)
|
|
if (buckled?.buckle_requires_restraints)
|
|
buckled.unbuckle_mob(src)
|
|
update_handcuffed()
|
|
if (client)
|
|
client.screen -= W
|
|
if (W)
|
|
W.forceMove(drop_location())
|
|
W.dropped(src)
|
|
if (W)
|
|
W.layer = initial(W.layer)
|
|
SET_PLANE_EXPLICIT(W, initial(W.plane), src)
|
|
changeNext_move(0)
|
|
if (legcuffed)
|
|
var/obj/item/W = legcuffed
|
|
legcuffed = null
|
|
update_worn_legcuffs()
|
|
if (client)
|
|
client.screen -= W
|
|
if (W)
|
|
W.forceMove(drop_location())
|
|
W.dropped(src)
|
|
if (W)
|
|
W.layer = initial(W.layer)
|
|
SET_PLANE_EXPLICIT(W, initial(W.plane), src)
|
|
changeNext_move(0)
|
|
|
|
/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break)
|
|
if(!I.loc || buckled)
|
|
return FALSE
|
|
if(I != handcuffed && I != legcuffed)
|
|
return FALSE
|
|
visible_message(span_danger("[src] manages to [cuff_break ? "break" : "remove"] [I]!"))
|
|
to_chat(src, span_notice("You successfully [cuff_break ? "break" : "remove"] [I]."))
|
|
|
|
if(cuff_break)
|
|
. = !((I == handcuffed) || (I == legcuffed))
|
|
qdel(I)
|
|
return TRUE
|
|
|
|
else
|
|
if(I == handcuffed)
|
|
handcuffed.forceMove(drop_location())
|
|
set_handcuffed(null)
|
|
I.dropped(src)
|
|
if(buckled?.buckle_requires_restraints)
|
|
buckled.unbuckle_mob(src)
|
|
update_handcuffed()
|
|
return TRUE
|
|
if(I == legcuffed)
|
|
legcuffed.forceMove(drop_location())
|
|
legcuffed = null
|
|
I.dropped(src)
|
|
update_worn_legcuffs()
|
|
return TRUE
|
|
|
|
/mob/living/carbon/proc/accident(obj/item/I)
|
|
if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP))
|
|
return
|
|
|
|
dropItemToGround(I)
|
|
|
|
var/modifier = 0
|
|
if(HAS_TRAIT(src, TRAIT_CLUMSY))
|
|
modifier -= 40 //Clumsy people are more likely to hit themselves -Honk!
|
|
|
|
switch(rand(1,100)+modifier) //91-100=Nothing special happens
|
|
if(-INFINITY to 0) //attack yourself
|
|
INVOKE_ASYNC(I, TYPE_PROC_REF(/obj/item, attack), src, src)
|
|
if(1 to 30) //throw it at yourself
|
|
I.throw_impact(src)
|
|
if(31 to 60) //Throw object in facing direction
|
|
var/turf/target = get_turf(loc)
|
|
var/range = rand(2,I.throw_range)
|
|
for(var/i in 1 to range-1)
|
|
var/turf/new_turf = get_step(target, dir)
|
|
target = new_turf
|
|
if(new_turf.density)
|
|
break
|
|
I.throw_at(target,I.throw_range,I.throw_speed,src)
|
|
if(61 to 90) //throw it down to the floor
|
|
var/turf/target = get_turf(loc)
|
|
I.safe_throw_at(target,I.throw_range,I.throw_speed,src, force = move_force)
|
|
|
|
/mob/living/carbon/attack_ui(slot, params)
|
|
if(!has_hand_for_held_index(active_hand_index))
|
|
return 0
|
|
return ..()
|
|
|
|
/// Proc that compels the mob to throw up. Returns TRUE if the mob actually threw up.
|
|
/mob/living/carbon/proc/vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/toxic, lost_nutrition = 10, distance = 1, purge_ratio = 0.1)
|
|
var/force = (vomit_flags & MOB_VOMIT_FORCE)
|
|
if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
|
|
return FALSE
|
|
|
|
if(!force && HAS_TRAIT(src, TRAIT_STRONG_STOMACH))
|
|
lost_nutrition *= 0.5
|
|
|
|
SEND_SIGNAL(src, COMSIG_CARBON_VOMITED, distance, force)
|
|
|
|
// cache some stuff that we'll need later (at least multiple times)
|
|
var/starting_dir = dir
|
|
var/message = (vomit_flags & MOB_VOMIT_MESSAGE)
|
|
var/stun = (vomit_flags & MOB_VOMIT_STUN)
|
|
var/knockdown = (vomit_flags & MOB_VOMIT_KNOCKDOWN)
|
|
var/blood = (vomit_flags & MOB_VOMIT_BLOOD)
|
|
|
|
if(!force && !blood && (nutrition < 100))
|
|
if(message)
|
|
visible_message(
|
|
span_warning("[src] dry heaves!"),
|
|
span_userdanger("You try to throw up, but there's nothing in your stomach!"),
|
|
)
|
|
if(stun)
|
|
var/stun_time = 20 SECONDS
|
|
if(HAS_TRAIT(src, TRAIT_STRONG_STOMACH))
|
|
stun_time *= 0.5
|
|
Stun(stun_time)
|
|
if(knockdown)
|
|
Knockdown(20 SECONDS)
|
|
return TRUE
|
|
|
|
if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious
|
|
if(message)
|
|
visible_message(
|
|
span_danger("[src] throws up all over [p_them()]self!"),
|
|
span_userdanger("You throw up all over yourself!"),
|
|
)
|
|
add_mood_event("vomit", /datum/mood_event/vomitself)
|
|
distance = 0
|
|
else
|
|
if(message)
|
|
visible_message(
|
|
span_danger("[src] throws up!"),
|
|
span_userdanger("You throw up!"),
|
|
)
|
|
if(!isflyperson(src))
|
|
add_mood_event("vomit", /datum/mood_event/vomit)
|
|
|
|
if(stun)
|
|
var/stun_time = 8 SECONDS
|
|
if(!blood && HAS_TRAIT(src, TRAIT_STRONG_STOMACH))
|
|
stun_time *= 0.5
|
|
Stun(stun_time)
|
|
if(knockdown)
|
|
Knockdown(8 SECONDS)
|
|
|
|
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
|
|
|
|
var/need_mob_update = FALSE
|
|
var/turf/location = get_turf(src)
|
|
if(!blood)
|
|
adjust_nutrition(-lost_nutrition)
|
|
need_mob_update += adjustToxLoss(-3, updating_health = FALSE)
|
|
|
|
for(var/i = 0 to distance)
|
|
if(blood)
|
|
if(location)
|
|
add_splatter_floor(location)
|
|
if(vomit_flags & MOB_VOMIT_HARM)
|
|
need_mob_update += adjustBruteLoss(3, updating_health = FALSE)
|
|
else
|
|
if(location)
|
|
location.add_vomit_floor(src, vomit_type, vomit_flags, purge_ratio) // call purge when doing detoxicfication to pump more chems out of the stomach.
|
|
|
|
location = get_step(location, starting_dir)
|
|
if (location?.is_blocked_turf())
|
|
break
|
|
if(need_mob_update) // so we only have to call updatehealth() once as opposed to n times
|
|
updatehealth()
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Expel the reagents you just tried to ingest
|
|
*
|
|
* When you try to ingest reagents but you do not have a stomach
|
|
* you will spew the reagents on the floor.
|
|
*
|
|
* Vars:
|
|
* * bite: /atom the reagents to expel
|
|
* * amount: int The amount of reagent
|
|
*/
|
|
/mob/living/carbon/proc/expel_ingested(atom/bite, amount)
|
|
visible_message(span_danger("[src] throws up all over [p_them()]self!"), \
|
|
span_userdanger("You are unable to keep the [bite] down without a stomach!"))
|
|
|
|
var/turf/floor = get_turf(src)
|
|
var/obj/effect/decal/cleanable/vomit/spew = new(floor, get_static_viruses())
|
|
bite.reagents.trans_to(spew, amount, transferred_by = src)
|
|
|
|
/mob/living/carbon/proc/spew_organ(power = 5, amt = 1)
|
|
for(var/i in 1 to amt)
|
|
if(!organs.len)
|
|
break //Guess we're out of organs!
|
|
var/obj/item/organ/guts = pick(organs)
|
|
var/turf/T = get_turf(src)
|
|
guts.Remove(src)
|
|
guts.forceMove(T)
|
|
var/atom/throw_target = get_edge_target_turf(guts, dir)
|
|
guts.throw_at(throw_target, power, 4, src)
|
|
|
|
|
|
/mob/living/carbon/fully_replace_character_name(oldname,newname)
|
|
. = ..()
|
|
if(dna)
|
|
dna.real_name = real_name
|
|
var/obj/item/bodypart/head/my_head = get_bodypart(BODY_ZONE_HEAD)
|
|
if(my_head)
|
|
my_head.real_name = real_name
|
|
|
|
|
|
/mob/living/carbon/set_body_position(new_value)
|
|
. = ..()
|
|
if(isnull(.))
|
|
return
|
|
if(new_value == LYING_DOWN)
|
|
add_movespeed_modifier(/datum/movespeed_modifier/carbon_crawling)
|
|
else
|
|
remove_movespeed_modifier(/datum/movespeed_modifier/carbon_crawling)
|
|
|
|
|
|
//Updates the mob's health from bodyparts and mob damage variables
|
|
/mob/living/carbon/updatehealth()
|
|
if(HAS_TRAIT(src, TRAIT_GODMODE))
|
|
return
|
|
var/total_burn = 0
|
|
var/total_brute = 0
|
|
for(var/X in bodyparts) //hardcoded to streamline things a bit
|
|
var/obj/item/bodypart/BP = X
|
|
total_brute += (BP.brute_dam * BP.body_damage_coeff)
|
|
total_burn += (BP.burn_dam * BP.body_damage_coeff)
|
|
set_health(round(maxHealth - getOxyLoss() - getToxLoss() - total_burn - total_brute, DAMAGE_PRECISION))
|
|
update_stat()
|
|
update_stamina()
|
|
|
|
/// The amount of burn damage needed to be done for this mob to be husked
|
|
var/husk_threshold = get_bodypart(BODY_ZONE_CHEST).max_damage * -1
|
|
|
|
if(((maxHealth - total_burn) < husk_threshold) && stat == DEAD )
|
|
become_husk(BURN)
|
|
med_hud_set_health()
|
|
if(stat == SOFT_CRIT)
|
|
add_movespeed_modifier(/datum/movespeed_modifier/carbon_softcrit)
|
|
else
|
|
remove_movespeed_modifier(/datum/movespeed_modifier/carbon_softcrit)
|
|
SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE)
|
|
|
|
/mob/living/carbon/update_sight()
|
|
if(!client)
|
|
return
|
|
if(stat == DEAD && !HAS_TRAIT(src, TRAIT_CORPSELOCKED))
|
|
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
|
|
set_sight(null)
|
|
else if(is_secret_level(z))
|
|
set_sight(initial(sight))
|
|
else
|
|
set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
|
|
set_invis_see(SEE_INVISIBLE_OBSERVER)
|
|
return
|
|
|
|
var/new_sight = initial(sight)
|
|
lighting_cutoff = initial(lighting_cutoff)
|
|
lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue)
|
|
|
|
var/obj/item/organ/eyes/eyes = get_organ_slot(ORGAN_SLOT_EYES)
|
|
if(eyes)
|
|
set_invis_see(eyes.see_invisible)
|
|
new_sight |= eyes.sight_flags
|
|
if(!isnull(eyes.lighting_cutoff))
|
|
lighting_cutoff = eyes.lighting_cutoff
|
|
if(!isnull(eyes.color_cutoffs))
|
|
lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, eyes.color_cutoffs)
|
|
|
|
if(client.eye && client.eye != src)
|
|
var/atom/A = client.eye
|
|
if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates.
|
|
return
|
|
|
|
if(glasses)
|
|
new_sight |= glasses.vision_flags
|
|
if(glasses.invis_override)
|
|
set_invis_see(glasses.invis_override)
|
|
else
|
|
set_invis_see(min(glasses.invis_view, see_invisible))
|
|
if(!isnull(glasses.lighting_cutoff))
|
|
lighting_cutoff = max(lighting_cutoff, glasses.lighting_cutoff)
|
|
if(length(glasses.color_cutoffs))
|
|
lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, glasses.color_cutoffs)
|
|
|
|
|
|
if(HAS_TRAIT(src, TRAIT_TRUE_NIGHT_VISION))
|
|
lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_HIGH)
|
|
|
|
if(HAS_TRAIT(src, TRAIT_MESON_VISION))
|
|
new_sight |= SEE_TURFS
|
|
lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_MEDIUM)
|
|
|
|
if(HAS_TRAIT(src, TRAIT_THERMAL_VISION))
|
|
new_sight |= SEE_MOBS
|
|
lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_MEDIUM)
|
|
|
|
if (HAS_TRAIT(src, TRAIT_MINOR_NIGHT_VISION))
|
|
lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_LOW)
|
|
|
|
if(HAS_TRAIT(src, TRAIT_XRAY_VISION))
|
|
new_sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
|
|
|
|
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
|
|
new_sight = NONE
|
|
|
|
set_sight(new_sight)
|
|
return ..()
|
|
|
|
/**
|
|
* Calculates how visually impaired the mob is by their equipment and other factors
|
|
*
|
|
* This is where clothing adds its various vision limiting effects, such as welding helmets
|
|
*/
|
|
/mob/living/carbon/proc/update_tint()
|
|
var/tint = 0
|
|
for(var/obj/item/clothing/worn_item in get_equipped_items())
|
|
tint += worn_item.tint
|
|
|
|
var/obj/item/organ/eyes/eyes = get_organ_slot(ORGAN_SLOT_EYES)
|
|
if(eyes)
|
|
tint += eyes.tint
|
|
|
|
if(tint >= TINT_BLIND)
|
|
become_blind(EYES_COVERED)
|
|
|
|
else if(tint >= TINT_DARKENED)
|
|
cure_blind(EYES_COVERED)
|
|
overlay_fullscreen("tint", /atom/movable/screen/fullscreen/impaired, 2)
|
|
|
|
else
|
|
cure_blind(EYES_COVERED)
|
|
clear_fullscreen("tint", 0 SECONDS)
|
|
|
|
//this handles hud updates
|
|
/mob/living/carbon/update_damage_hud()
|
|
|
|
if(!client)
|
|
return
|
|
|
|
if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOCRITOVERLAY))
|
|
var/severity = 0
|
|
switch(health)
|
|
if(-20 to -10)
|
|
severity = 1
|
|
if(-30 to -20)
|
|
severity = 2
|
|
if(-40 to -30)
|
|
severity = 3
|
|
if(-50 to -40)
|
|
severity = 4
|
|
if(-50 to -40)
|
|
severity = 5
|
|
if(-60 to -50)
|
|
severity = 6
|
|
if(-70 to -60)
|
|
severity = 7
|
|
if(-90 to -70)
|
|
severity = 8
|
|
if(-95 to -90)
|
|
severity = 9
|
|
if(-INFINITY to -95)
|
|
severity = 10
|
|
if(stat != HARD_CRIT)
|
|
var/visionseverity = 4
|
|
switch(health)
|
|
if(-8 to -4)
|
|
visionseverity = 5
|
|
if(-12 to -8)
|
|
visionseverity = 6
|
|
if(-16 to -12)
|
|
visionseverity = 7
|
|
if(-20 to -16)
|
|
visionseverity = 8
|
|
if(-24 to -20)
|
|
visionseverity = 9
|
|
if(-INFINITY to -24)
|
|
visionseverity = 10
|
|
overlay_fullscreen("critvision", /atom/movable/screen/fullscreen/crit/vision, visionseverity)
|
|
else
|
|
clear_fullscreen("critvision")
|
|
overlay_fullscreen("crit", /atom/movable/screen/fullscreen/crit, severity)
|
|
else
|
|
clear_fullscreen("crit")
|
|
clear_fullscreen("critvision")
|
|
|
|
//Oxygen damage overlay
|
|
if(oxyloss)
|
|
var/severity = 0
|
|
switch(oxyloss)
|
|
if(10 to 20)
|
|
severity = 1
|
|
if(20 to 25)
|
|
severity = 2
|
|
if(25 to 30)
|
|
severity = 3
|
|
if(30 to 35)
|
|
severity = 4
|
|
if(35 to 40)
|
|
severity = 5
|
|
if(40 to 45)
|
|
severity = 6
|
|
if(45 to INFINITY)
|
|
severity = 7
|
|
overlay_fullscreen("oxy", /atom/movable/screen/fullscreen/oxy, severity)
|
|
else
|
|
clear_fullscreen("oxy")
|
|
|
|
//Fire and Brute damage overlay (BSSR)
|
|
var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp
|
|
if(hurtdamage && !HAS_TRAIT(src, TRAIT_NO_DAMAGE_OVERLAY))
|
|
var/severity = 0
|
|
switch(hurtdamage)
|
|
if(5 to 15)
|
|
severity = 1
|
|
if(15 to 30)
|
|
severity = 2
|
|
if(30 to 45)
|
|
severity = 3
|
|
if(45 to 70)
|
|
severity = 4
|
|
if(70 to 85)
|
|
severity = 5
|
|
if(85 to INFINITY)
|
|
severity = 6
|
|
overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
|
|
else
|
|
clear_fullscreen("brute")
|
|
|
|
/mob/living/carbon/update_health_hud(shown_health_amount)
|
|
if(!client || !hud_used?.healths)
|
|
return
|
|
|
|
if(stat == DEAD)
|
|
hud_used.healths.icon_state = "health7"
|
|
return
|
|
|
|
if(SEND_SIGNAL(src, COMSIG_CARBON_UPDATING_HEALTH_HUD, shown_health_amount) & COMPONENT_OVERRIDE_HEALTH_HUD)
|
|
return
|
|
|
|
if(shown_health_amount == null)
|
|
shown_health_amount = health
|
|
|
|
if(shown_health_amount >= maxHealth)
|
|
hud_used.healths.icon_state = "health0"
|
|
|
|
else if(shown_health_amount > maxHealth * 0.8)
|
|
hud_used.healths.icon_state = "health1"
|
|
|
|
else if(shown_health_amount > maxHealth * 0.6)
|
|
hud_used.healths.icon_state = "health2"
|
|
|
|
else if(shown_health_amount > maxHealth * 0.4)
|
|
hud_used.healths.icon_state = "health3"
|
|
|
|
else if(shown_health_amount > maxHealth*0.2)
|
|
hud_used.healths.icon_state = "health4"
|
|
|
|
else if(shown_health_amount > 0)
|
|
hud_used.healths.icon_state = "health5"
|
|
|
|
else
|
|
hud_used.healths.icon_state = "health6"
|
|
|
|
/mob/living/carbon/update_stamina_hud(shown_stamina_loss)
|
|
if(!client || !hud_used?.stamina)
|
|
return
|
|
|
|
var/stam_crit_threshold = maxHealth - crit_threshold
|
|
|
|
if(stat == DEAD)
|
|
hud_used.stamina.icon_state = "stamina_dead"
|
|
else
|
|
|
|
if(shown_stamina_loss == null)
|
|
shown_stamina_loss = getStaminaLoss()
|
|
|
|
if(shown_stamina_loss >= stam_crit_threshold)
|
|
hud_used.stamina.icon_state = "stamina_crit"
|
|
else if(shown_stamina_loss > maxHealth*0.8)
|
|
hud_used.stamina.icon_state = "stamina_5"
|
|
else if(shown_stamina_loss > maxHealth*0.6)
|
|
hud_used.stamina.icon_state = "stamina_4"
|
|
else if(shown_stamina_loss > maxHealth*0.4)
|
|
hud_used.stamina.icon_state = "stamina_3"
|
|
else if(shown_stamina_loss > maxHealth*0.2)
|
|
hud_used.stamina.icon_state = "stamina_2"
|
|
else if(shown_stamina_loss > 0)
|
|
hud_used.stamina.icon_state = "stamina_1"
|
|
else
|
|
hud_used.stamina.icon_state = "stamina_full"
|
|
|
|
/mob/living/carbon/proc/update_spacesuit_hud_icon(cell_state = "empty")
|
|
hud_used?.spacesuit?.icon_state = "spacesuit_[cell_state]"
|
|
|
|
/mob/living/carbon/set_health(new_value)
|
|
. = ..()
|
|
if(. > hardcrit_threshold)
|
|
if(health <= hardcrit_threshold && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))
|
|
ADD_TRAIT(src, TRAIT_KNOCKEDOUT, CRIT_HEALTH_TRAIT)
|
|
else if(health > hardcrit_threshold)
|
|
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, CRIT_HEALTH_TRAIT)
|
|
if(CONFIG_GET(flag/near_death_experience))
|
|
if(. > HEALTH_THRESHOLD_NEARDEATH)
|
|
if(health <= HEALTH_THRESHOLD_NEARDEATH && !HAS_TRAIT(src, TRAIT_NODEATH))
|
|
ADD_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
|
|
else if(health > HEALTH_THRESHOLD_NEARDEATH)
|
|
REMOVE_TRAIT(src, TRAIT_SIXTHSENSE, "near-death")
|
|
|
|
|
|
/mob/living/carbon/update_stat()
|
|
if(HAS_TRAIT(src, TRAIT_GODMODE))
|
|
return
|
|
if(stat != DEAD)
|
|
if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH))
|
|
death()
|
|
return
|
|
if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED, AUTOPSY_TRAIT))
|
|
REMOVE_TRAIT(src, TRAIT_DISSECTED, AUTOPSY_TRAIT)
|
|
if(health <= hardcrit_threshold && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))
|
|
set_stat(HARD_CRIT)
|
|
else if(HAS_TRAIT(src, TRAIT_KNOCKEDOUT))
|
|
set_stat(UNCONSCIOUS)
|
|
else if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT))
|
|
set_stat(SOFT_CRIT)
|
|
else
|
|
set_stat(CONSCIOUS)
|
|
update_damage_hud()
|
|
update_health_hud()
|
|
update_stamina_hud()
|
|
med_hud_set_status()
|
|
|
|
|
|
//called when we get cuffed/uncuffed
|
|
/mob/living/carbon/proc/update_handcuffed()
|
|
if(handcuffed)
|
|
drop_all_held_items()
|
|
stop_pulling()
|
|
throw_alert(ALERT_HANDCUFFED, /atom/movable/screen/alert/restrained/handcuffed, new_master = src.handcuffed)
|
|
add_mood_event("handcuffed", /datum/mood_event/handcuffed)
|
|
else
|
|
clear_alert(ALERT_HANDCUFFED)
|
|
clear_mood_event("handcuffed")
|
|
update_mob_action_buttons() //some of our action buttons might be unusable when we're handcuffed.
|
|
update_worn_handcuffs()
|
|
update_hud_handcuffed()
|
|
|
|
/mob/living/carbon/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
|
|
if(excess_healing)
|
|
if(dna && !HAS_TRAIT(src, TRAIT_NOBLOOD))
|
|
blood_volume += (excess_healing * 2) //1 excess = 10 blood
|
|
|
|
for(var/obj/item/organ/target_organ as anything in organs)
|
|
if(!target_organ.damage)
|
|
continue
|
|
|
|
target_organ.apply_organ_damage(excess_healing * -1, required_organ_flag = ORGAN_ORGANIC) //1 excess = 5 organ damage healed
|
|
|
|
return ..()
|
|
|
|
/mob/living/carbon/heal_and_revive(heal_to = 75, revive_message)
|
|
// We can't heal them if they're missing a heart
|
|
if(needs_heart() && !get_organ_slot(ORGAN_SLOT_HEART))
|
|
return FALSE
|
|
|
|
// We can't heal them if they're missing their lungs
|
|
if(!HAS_TRAIT(src, TRAIT_NOBREATH) && !isnull(dna?.species.mutantlungs) && !get_organ_slot(ORGAN_SLOT_LUNGS))
|
|
return FALSE
|
|
|
|
// And we can't heal them if they're missing their liver
|
|
if(!HAS_TRAIT(src, TRAIT_LIVERLESS_METABOLISM) && !isnull(dna?.species.mutantliver) && !get_organ_slot(ORGAN_SLOT_LIVER))
|
|
return FALSE
|
|
|
|
. = ..()
|
|
if(.) // if revived successfully
|
|
set_heartattack(FALSE)
|
|
|
|
return .
|
|
|
|
/mob/living/carbon/fully_heal(heal_flags = HEAL_ALL)
|
|
|
|
// Should be handled via signal on embedded, or via heal on bodypart
|
|
// Otherwise I don't care to give it a separate flag
|
|
remove_all_embedded_objects()
|
|
|
|
if(heal_flags & HEAL_NEGATIVE_DISEASES)
|
|
for(var/datum/disease/disease as anything in diseases)
|
|
if(disease.severity != DISEASE_SEVERITY_POSITIVE)
|
|
disease.cure(FALSE)
|
|
|
|
if(heal_flags & HEAL_WOUNDS)
|
|
for(var/datum/wound/wound as anything in all_wounds)
|
|
wound.remove_wound()
|
|
|
|
if(heal_flags & HEAL_LIMBS)
|
|
regenerate_limbs()
|
|
|
|
if(heal_flags & (HEAL_REFRESH_ORGANS|HEAL_ORGANS))
|
|
regenerate_organs(remove_hazardous = !!(heal_flags & HEAL_REFRESH_ORGANS))
|
|
|
|
if(heal_flags & HEAL_TRAUMAS)
|
|
cure_all_traumas(TRAUMA_RESILIENCE_MAGIC)
|
|
// Addictions are like traumas
|
|
if(mind)
|
|
for(var/addiction_type in subtypesof(/datum/addiction))
|
|
mind.remove_addiction_points(addiction_type, MAX_ADDICTION_POINTS) //Remove the addiction!
|
|
|
|
if(heal_flags & HEAL_RESTRAINTS)
|
|
QDEL_NULL(handcuffed)
|
|
QDEL_NULL(legcuffed)
|
|
set_handcuffed(null)
|
|
update_handcuffed()
|
|
|
|
return ..()
|
|
|
|
/mob/living/carbon/do_strange_reagent_revival(healing_amount)
|
|
set_heartattack(FALSE)
|
|
return ..()
|
|
|
|
/mob/living/carbon/can_be_revived()
|
|
if(!get_organ_by_type(/obj/item/organ/brain) && (!IS_CHANGELING(src)) || HAS_TRAIT(src, TRAIT_HUSK))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/mob/living/carbon/proc/can_defib()
|
|
if (HAS_TRAIT(src, TRAIT_SUICIDED))
|
|
return DEFIB_FAIL_SUICIDE
|
|
|
|
if (HAS_TRAIT(src, TRAIT_HUSK))
|
|
return DEFIB_FAIL_HUSK
|
|
|
|
if (HAS_TRAIT(src, TRAIT_DEFIB_BLACKLISTED))
|
|
return DEFIB_FAIL_BLACKLISTED
|
|
|
|
if ((getBruteLoss() >= MAX_REVIVE_BRUTE_DAMAGE) || (getFireLoss() >= MAX_REVIVE_FIRE_DAMAGE))
|
|
return DEFIB_FAIL_TISSUE_DAMAGE
|
|
|
|
// Only check for a heart if they actually need a heart. Who would've thunk
|
|
if (needs_heart())
|
|
var/obj/item/organ/heart = get_organ_by_type(/obj/item/organ/heart)
|
|
|
|
if (!heart)
|
|
return DEFIB_FAIL_NO_HEART
|
|
|
|
if (heart.organ_flags & ORGAN_FAILING)
|
|
return DEFIB_FAIL_FAILING_HEART
|
|
|
|
var/obj/item/organ/brain/current_brain = get_organ_by_type(/obj/item/organ/brain)
|
|
|
|
if (QDELETED(current_brain))
|
|
return DEFIB_FAIL_NO_BRAIN
|
|
|
|
if (current_brain.organ_flags & ORGAN_FAILING)
|
|
return DEFIB_FAIL_FAILING_BRAIN
|
|
|
|
if (current_brain.suicided || (current_brain.brainmob && HAS_TRAIT(current_brain.brainmob, TRAIT_SUICIDED)))
|
|
return DEFIB_FAIL_NO_INTELLIGENCE
|
|
|
|
if(IS_FAKE_KEY(key))
|
|
return DEFIB_NOGRAB_AGHOST
|
|
|
|
return DEFIB_POSSIBLE
|
|
|
|
/mob/living/carbon/proc/can_defib_client()
|
|
return (client || get_ghost(FALSE, TRUE)) && (can_defib() & DEFIB_REVIVABLE_STATES)
|
|
|
|
/mob/living/carbon/harvest(mob/living/user)
|
|
if(QDELETED(src))
|
|
return
|
|
var/organs_amt = 0
|
|
for(var/obj/item/organ/organ as anything in organs)
|
|
if(prob(50))
|
|
organs_amt++
|
|
organ.Remove(src)
|
|
organ.forceMove(drop_location())
|
|
if(organs_amt)
|
|
to_chat(user, span_notice("You retrieve some of [src]\'s internal organs!"))
|
|
remove_all_embedded_objects()
|
|
|
|
/// Creates body parts for this carbon completely from scratch.
|
|
/// Optionally takes a map of body zones to what type to instantiate instead of them.
|
|
/mob/living/carbon/proc/create_bodyparts(list/overrides)
|
|
var/list/bodyparts_paths = overrides?.Copy() || bodyparts.Copy()
|
|
bodyparts = list()
|
|
for(var/obj/item/bodypart/bodypart_path as anything in bodyparts_paths)
|
|
var/real_body_part_path = overrides?[initial(bodypart_path.body_zone)] || bodypart_path
|
|
var/obj/item/bodypart/bodypart_instance = new real_body_part_path()
|
|
bodypart_instance.try_attach_limb(src, FALSE, TRUE)
|
|
|
|
bodyparts = sort_list(bodyparts, GLOBAL_PROC_REF(cmp_bodypart_by_body_part_asc))
|
|
|
|
/// Called when a new hand is added
|
|
/mob/living/carbon/proc/on_added_hand(obj/item/bodypart/arm/new_hand, hand_index)
|
|
if(hand_index > hand_bodyparts.len)
|
|
hand_bodyparts.len = hand_index
|
|
hand_bodyparts[hand_index] = new_hand
|
|
|
|
/// Cleans up references to a hand when it is dismembered or deleted
|
|
/mob/living/carbon/proc/on_lost_hand(obj/item/bodypart/arm/lost_hand)
|
|
hand_bodyparts[lost_hand.held_index] = null
|
|
|
|
///Proc to hook behavior on bodypart additions. Do not directly call. You're looking for [/obj/item/bodypart/proc/try_attach_limb()].
|
|
/mob/living/carbon/proc/add_bodypart(obj/item/bodypart/new_bodypart)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
new_bodypart.on_adding(src)
|
|
bodyparts += new_bodypart
|
|
new_bodypart.update_owner(src)
|
|
|
|
// Apply a bodypart effect or merge with an existing one, for stuff like plant limbs regenning in light
|
|
for(var/datum/status_effect/grouped/bodypart_effect/effect_type as anything in new_bodypart.bodypart_effects)
|
|
apply_status_effect(effect_type, type, new_bodypart)
|
|
|
|
// Tell the organs in the bodyparts that we are in a mob again
|
|
for(var/obj/item/organ/organ in new_bodypart)
|
|
organ.mob_insert(src)
|
|
|
|
switch(new_bodypart.body_part)
|
|
if(LEG_LEFT, LEG_RIGHT)
|
|
set_num_legs(num_legs + 1)
|
|
if(!new_bodypart.bodypart_disabled)
|
|
set_usable_legs(usable_legs + 1)
|
|
if(ARM_LEFT, ARM_RIGHT)
|
|
set_num_hands(num_hands + 1)
|
|
if(!new_bodypart.bodypart_disabled)
|
|
set_usable_hands(usable_hands + 1)
|
|
|
|
synchronize_bodytypes()
|
|
synchronize_bodyshapes()
|
|
///Proc to hook behavior on bodypart removals. Do not directly call. You're looking for [/obj/item/bodypart/proc/drop_limb()].
|
|
/mob/living/carbon/proc/remove_bodypart(obj/item/bodypart/old_bodypart, special)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
if(special)
|
|
for(var/obj/item/organ/organ in old_bodypart)
|
|
organ.bodypart_remove(limb_owner = src, movement_flags = NO_ID_TRANSFER)
|
|
else
|
|
for(var/obj/item/organ/organ in old_bodypart)
|
|
organ.mob_remove(src, special)
|
|
|
|
old_bodypart.on_removal(src)
|
|
bodyparts -= old_bodypart
|
|
|
|
switch(old_bodypart.body_part)
|
|
if(LEG_LEFT, LEG_RIGHT)
|
|
set_num_legs(num_legs - 1)
|
|
if(!old_bodypart.bodypart_disabled)
|
|
set_usable_legs(usable_legs - 1)
|
|
if(ARM_LEFT, ARM_RIGHT)
|
|
set_num_hands(num_hands - 1)
|
|
if(!old_bodypart.bodypart_disabled)
|
|
set_usable_hands(usable_hands - 1)
|
|
|
|
synchronize_bodytypes()
|
|
synchronize_bodyshapes()
|
|
|
|
///Updates the bodypart speed modifier based on our bodyparts.
|
|
/mob/living/carbon/proc/update_bodypart_speed_modifier()
|
|
var/final_modification = 0
|
|
for(var/obj/item/bodypart/bodypart as anything in bodyparts)
|
|
final_modification += bodypart.speed_modifier
|
|
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bodypart, update = TRUE, multiplicative_slowdown = final_modification)
|
|
|
|
/proc/cmp_organ_slot_asc(slot_a, slot_b)
|
|
return GLOB.organ_process_order.Find(slot_a) - GLOB.organ_process_order.Find(slot_b)
|
|
|
|
/mob/living/carbon/proc/get_footprint_sprite()
|
|
return FOOTPRINT_SPRITE_PAWS
|
|
|
|
/mob/living/carbon/vv_get_dropdown()
|
|
. = ..()
|
|
VV_DROPDOWN_OPTION("", "---------")
|
|
VV_DROPDOWN_OPTION(VV_HK_MODIFY_BODYPART, "Modify bodypart")
|
|
VV_DROPDOWN_OPTION(VV_HK_MODIFY_ORGANS, "Modify organs")
|
|
VV_DROPDOWN_OPTION(VV_HK_MARTIAL_ART, "Give Martial Arts")
|
|
VV_DROPDOWN_OPTION(VV_HK_GIVE_TRAUMA, "Give Brain Trauma")
|
|
VV_DROPDOWN_OPTION(VV_HK_CURE_TRAUMA, "Cure Brain Traumas")
|
|
|
|
/mob/living/carbon/vv_do_topic(list/href_list)
|
|
. = ..()
|
|
|
|
if(!.)
|
|
return
|
|
|
|
if(href_list[VV_HK_MODIFY_BODYPART])
|
|
if(!check_rights(R_SPAWN))
|
|
return
|
|
var/edit_action = input(usr, "What would you like to do?","Modify Body Part") as null|anything in list("replace","remove")
|
|
if(!edit_action)
|
|
return
|
|
var/list/limb_list = list()
|
|
if(edit_action == "remove")
|
|
for(var/obj/item/bodypart/iter_part as anything in bodyparts)
|
|
limb_list += iter_part.body_zone
|
|
limb_list -= BODY_ZONE_CHEST
|
|
else
|
|
limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG, BODY_ZONE_CHEST)
|
|
var/result = input(usr, "Please choose which bodypart to [edit_action]","[capitalize(edit_action)] Bodypart") as null|anything in sort_list(limb_list)
|
|
if(result)
|
|
var/obj/item/bodypart/part = get_bodypart(result)
|
|
var/list/limbtypes = list()
|
|
switch(result)
|
|
if(BODY_ZONE_CHEST)
|
|
limbtypes = typesof(/obj/item/bodypart/chest)
|
|
if(BODY_ZONE_R_ARM)
|
|
limbtypes = typesof(/obj/item/bodypart/arm/right)
|
|
if(BODY_ZONE_L_ARM)
|
|
limbtypes = typesof(/obj/item/bodypart/arm/left)
|
|
if(BODY_ZONE_HEAD)
|
|
limbtypes = typesof(/obj/item/bodypart/head)
|
|
if(BODY_ZONE_L_LEG)
|
|
limbtypes = typesof(/obj/item/bodypart/leg/left)
|
|
if(BODY_ZONE_R_LEG)
|
|
limbtypes = typesof(/obj/item/bodypart/leg/right)
|
|
switch(edit_action)
|
|
if("remove")
|
|
if(part)
|
|
part.drop_limb()
|
|
admin_ticket_log("[key_name_admin(usr)] has removed [src]'s [part.plaintext_zone]")
|
|
else
|
|
to_chat(usr, span_boldwarning("[src] doesn't have such bodypart."))
|
|
admin_ticket_log("[key_name_admin(usr)] has attempted to modify the bodyparts of [src]")
|
|
if("replace")
|
|
var/limb2add = input(usr, "Select a bodypart type to add", "Add/Replace Bodypart") as null|anything in sort_list(limbtypes)
|
|
var/obj/item/bodypart/new_bp = new limb2add()
|
|
if(new_bp.replace_limb(src, special = TRUE))
|
|
admin_ticket_log("key_name_admin(usr)] has replaced [src]'s [part.type] with [new_bp.type]")
|
|
qdel(part)
|
|
else
|
|
to_chat(usr, "Failed to replace bodypart! They might be incompatible.")
|
|
admin_ticket_log("[key_name_admin(usr)] has attempted to modify the bodyparts of [src]")
|
|
|
|
if(href_list[VV_HK_MODIFY_ORGANS])
|
|
return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/manipulate_organs, src)
|
|
|
|
if(href_list[VV_HK_MARTIAL_ART])
|
|
if(!check_rights(NONE))
|
|
return
|
|
var/list/artpaths = subtypesof(/datum/martial_art)
|
|
var/list/artnames = list()
|
|
for(var/i in artpaths)
|
|
var/datum/martial_art/M = i
|
|
artnames[initial(M.name)] = M
|
|
var/result = input(usr, "Choose the martial art to teach","JUDO CHOP") as null|anything in sort_list(artnames, GLOBAL_PROC_REF(cmp_typepaths_asc))
|
|
if(!usr)
|
|
return
|
|
if(QDELETED(src))
|
|
to_chat(usr, span_boldwarning("Mob doesn't exist anymore."))
|
|
return
|
|
if(result)
|
|
var/chosenart = artnames[result]
|
|
var/datum/martial_art/MA = new chosenart(src)
|
|
MA.teach(src)
|
|
log_admin("[key_name(usr)] has taught [MA] to [key_name(src)].")
|
|
message_admins(span_notice("[key_name_admin(usr)] has taught [MA] to [key_name_admin(src)]."))
|
|
|
|
if(href_list[VV_HK_GIVE_TRAUMA])
|
|
if(!check_rights(NONE))
|
|
return
|
|
var/list/traumas = subtypesof(/datum/brain_trauma)
|
|
var/result = input(usr, "Choose the brain trauma to apply","Traumatize") as null|anything in sort_list(traumas, GLOBAL_PROC_REF(cmp_typepaths_asc))
|
|
if(!usr)
|
|
return
|
|
if(QDELETED(src))
|
|
to_chat(usr, "Mob doesn't exist anymore")
|
|
return
|
|
if(!result)
|
|
return
|
|
var/datum/brain_trauma/BT = gain_trauma(result)
|
|
if(BT)
|
|
log_admin("[key_name(usr)] has traumatized [key_name(src)] with [BT.name]")
|
|
message_admins(span_notice("[key_name_admin(usr)] has traumatized [key_name_admin(src)] with [BT.name]."))
|
|
|
|
if(href_list[VV_HK_CURE_TRAUMA])
|
|
if(!check_rights(NONE))
|
|
return
|
|
cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE)
|
|
log_admin("[key_name(usr)] has cured all traumas from [key_name(src)].")
|
|
message_admins(span_notice("[key_name_admin(usr)] has cured all traumas from [key_name_admin(src)]."))
|
|
|
|
/mob/living/carbon/can_resist()
|
|
return bodyparts.len > 2 && ..()
|
|
|
|
/mob/living/carbon/proc/hypnosis_vulnerable()
|
|
if(HAS_MIND_TRAIT(src, TRAIT_UNCONVERTABLE))
|
|
return FALSE
|
|
if(has_status_effect(/datum/status_effect/hallucination) || has_status_effect(/datum/status_effect/drugginess))
|
|
return TRUE
|
|
if(IsSleeping() || IsUnconscious())
|
|
return TRUE
|
|
if(HAS_TRAIT(src, TRAIT_DUMB))
|
|
return TRUE
|
|
if(mob_mood.sanity < SANITY_UNSTABLE)
|
|
return TRUE
|
|
|
|
/mob/living/carbon/wash(clean_types)
|
|
. = ..()
|
|
// Wash equipped stuff that cannot be covered
|
|
for(var/obj/item/held_thing in held_items)
|
|
. |= held_thing.wash(clean_types)
|
|
|
|
// Check and wash stuff that isn't covered
|
|
var/covered = check_covered_slots()
|
|
for(var/obj/item/worn as anything in get_equipped_items())
|
|
var/slot = get_slot_by_item(worn)
|
|
// Don't wash glasses if something other than them is covering our eyes
|
|
if(slot == ITEM_SLOT_EYES && is_eyes_covered(ITEM_SLOT_MASK|ITEM_SLOT_HEAD))
|
|
continue
|
|
if(!(covered & slot))
|
|
// /obj/item/wash() already updates our clothing slot
|
|
. = worn.wash(clean_types) || .
|
|
|
|
/// if any of our bodyparts are bleeding
|
|
/mob/living/carbon/proc/is_bleeding()
|
|
for(var/obj/item/bodypart/part as anything in bodyparts)
|
|
if(part.cached_bleed_rate)
|
|
return TRUE
|
|
|
|
/// get our total bleedrate
|
|
/mob/living/carbon/proc/get_total_bleed_rate()
|
|
var/total_bleed_rate = 0
|
|
for(var/obj/item/bodypart/part as anything in bodyparts)
|
|
total_bleed_rate += part.cached_bleed_rate
|
|
|
|
return total_bleed_rate
|
|
|
|
/**
|
|
* generate_fake_scars()- for when you want to scar someone, but you don't want to hurt them first. These scars don't count for temporal scarring (hence, fake)
|
|
*
|
|
* If you want a specific wound scar, pass that wound type as the second arg, otherwise you can pass a list like WOUND_LIST_SLASH to generate a random cut scar.
|
|
*
|
|
* Arguments:
|
|
* * num_scars- A number for how many scars you want to add
|
|
* * forced_type- Which wound or category of wounds you want to choose from, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN (or some combination). If passed a list, picks randomly from the listed wounds. Defaults to all 3 types
|
|
*/
|
|
/mob/living/carbon/proc/generate_fake_scars(num_scars, forced_type)
|
|
for(var/i in 1 to num_scars)
|
|
var/datum/scar/scaries = new
|
|
var/obj/item/bodypart/scar_part = pick(bodyparts)
|
|
|
|
var/wound_type
|
|
if(forced_type)
|
|
if(islist(forced_type))
|
|
wound_type = pick(forced_type)
|
|
else
|
|
wound_type = forced_type
|
|
else
|
|
for (var/datum/wound/path as anything in shuffle(GLOB.all_wound_pregen_data))
|
|
var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[path]
|
|
if (pregen_data.can_be_applied_to(scar_part, random_roll = TRUE))
|
|
wound_type = path
|
|
break
|
|
|
|
if (wound_type) // can feasibly happen, if its an inorganic limb/cant be wounded/scarred
|
|
var/datum/wound/phantom_wound = new wound_type
|
|
scaries.generate(scar_part, phantom_wound)
|
|
scaries.fake = TRUE
|
|
QDEL_NULL(phantom_wound)
|
|
|
|
/mob/living/carbon/is_face_visible()
|
|
return !(wear_mask?.flags_inv & HIDEFACE) && !(head?.flags_inv & HIDEFACE)
|
|
|
|
/// Returns whether or not the carbon should be able to be shocked
|
|
/mob/living/carbon/proc/should_electrocute(power_source)
|
|
if (ismecha(loc))
|
|
return FALSE
|
|
|
|
if (wearing_shock_proof_gloves())
|
|
return FALSE
|
|
|
|
if(!get_powernet_info_from_source(power_source))
|
|
return FALSE
|
|
|
|
if (HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Returns if the carbon is wearing shock proof gloves
|
|
/mob/living/carbon/proc/wearing_shock_proof_gloves()
|
|
return gloves?.siemens_coefficient == 0
|
|
|
|
/// Modifies max_skillchip_count and updates active skillchips
|
|
/mob/living/carbon/proc/adjust_skillchip_complexity_modifier(delta)
|
|
skillchip_complexity_modifier += delta
|
|
|
|
var/obj/item/organ/brain/brain = get_organ_slot(ORGAN_SLOT_BRAIN)
|
|
|
|
if(!brain)
|
|
return
|
|
|
|
brain.update_skillchips()
|
|
|
|
|
|
/// Modifies the handcuffed value if a different value is passed, returning FALSE otherwise. The variable should only be changed through this proc.
|
|
/mob/living/carbon/proc/set_handcuffed(new_value)
|
|
if(handcuffed == new_value)
|
|
return FALSE
|
|
. = handcuffed
|
|
handcuffed = new_value
|
|
if(.)
|
|
if(!handcuffed)
|
|
REMOVE_TRAIT(src, TRAIT_RESTRAINED, HANDCUFFED_TRAIT)
|
|
else if(handcuffed)
|
|
ADD_TRAIT(src, TRAIT_RESTRAINED, HANDCUFFED_TRAIT)
|
|
|
|
|
|
/mob/living/carbon/on_lying_down(new_lying_angle)
|
|
. = ..()
|
|
if(!buckled || buckled.buckle_lying != 0)
|
|
lying_angle_on_lying_down(new_lying_angle)
|
|
|
|
|
|
/// Special carbon interaction on lying down, to transform its sprite by a rotation.
|
|
/mob/living/carbon/proc/lying_angle_on_lying_down(new_lying_angle)
|
|
if(!new_lying_angle)
|
|
set_lying_angle(pick(LYING_ANGLE_EAST, LYING_ANGLE_WEST))
|
|
else
|
|
set_lying_angle(new_lying_angle)
|
|
|
|
/mob/living/carbon/vv_edit_var(var_name, var_value)
|
|
switch(var_name)
|
|
if(NAMEOF(src, disgust))
|
|
set_disgust(var_value)
|
|
. = TRUE
|
|
if(NAMEOF(src, handcuffed))
|
|
set_handcuffed(var_value)
|
|
. = TRUE
|
|
|
|
if(!isnull(.))
|
|
datum_flags |= DF_VAR_EDITED
|
|
return
|
|
|
|
return ..()
|
|
|
|
/mob/living/carbon/get_attack_type()
|
|
if(has_active_hand())
|
|
var/obj/item/bodypart/arm/active_arm = get_active_hand()
|
|
return active_arm.attack_type
|
|
return ..()
|
|
|
|
/mob/living/carbon/proc/attach_rot()
|
|
if(flags_1 & HOLOGRAM_1)
|
|
return
|
|
if(!(mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)))
|
|
return
|
|
AddComponent(/datum/component/rot, 6 MINUTES, 10 MINUTES, 1)
|
|
|
|
/mob/living/carbon/get_photo_description(obj/item/camera/camera)
|
|
if(HAS_TRAIT(src, TRAIT_INVISIBLE_TO_CAMERA))
|
|
if(camera.see_ghosts)
|
|
return /mob/dead/observer::photo_description
|
|
return null
|
|
return ..()
|
|
|
|
/**
|
|
* This proc is used to determine whether or not the mob can handle touching an acid affected object.
|
|
*/
|
|
/mob/living/carbon/proc/can_touch_acid(atom/acided_atom, acid_power, acid_volume)
|
|
// So people can take their own clothes off
|
|
if((acided_atom == src) || (acided_atom.loc == src))
|
|
return TRUE
|
|
if((acid_power * acid_volume) < ACID_LEVEL_HANDBURN)
|
|
return TRUE
|
|
if(gloves?.resistance_flags & (UNACIDABLE | ACID_PROOF))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/**
|
|
* This proc is used to determine whether or not the mob can handle touching a burning object.
|
|
*/
|
|
/mob/living/carbon/proc/can_touch_burning(atom/burning_atom, acid_power, acid_volume)
|
|
// So people can take their own clothes off
|
|
if((burning_atom == src) || (burning_atom.loc == src))
|
|
return TRUE
|
|
if(HAS_TRAIT(src, TRAIT_RESISTHEAT) || HAS_TRAIT(src, TRAIT_RESISTHEATHANDS))
|
|
return TRUE
|
|
if(gloves?.max_heat_protection_temperature >= BURNING_ITEM_MINIMUM_TEMPERATURE)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Goes through the organs and bodyparts of the mob and updates their blood_dna_info, in case their blood type has changed (via set_species() or otherwise)
|
|
/mob/living/carbon/proc/update_cached_blood_dna_info()
|
|
var/list/blood_dna_info = get_blood_dna_list()
|
|
for(var/obj/item/organ/organ in organs)
|
|
organ.blood_dna_info = blood_dna_info
|
|
for(var/obj/item/bodypart/bodypart in bodyparts)
|
|
bodypart.blood_dna_info = blood_dna_info
|
|
|
|
/// Setter for changing a mob's blood type
|
|
/mob/living/carbon/proc/set_blood_type(datum/blood_type/new_blood_type, update_cached_blood_dna_info = TRUE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
if(isnull(dna))
|
|
return
|
|
|
|
if(istext(new_blood_type))
|
|
new_blood_type = get_blood_type(new_blood_type)
|
|
if(!istype(new_blood_type))
|
|
return
|
|
|
|
if(get_bloodtype() == new_blood_type) // already has this blood type, we don't need to do anything.
|
|
return
|
|
|
|
dna.blood_type = new_blood_type
|
|
if(update_cached_blood_dna_info)
|
|
update_cached_blood_dna_info()
|
|
SEND_SIGNAL(src, COMSIG_CARBON_CHANGED_BLOOD_TYPE, new_blood_type, update_cached_blood_dna_info)
|
|
|
|
/mob/living/carbon/dropItemToGround(obj/item/to_drop, force = FALSE, silent = FALSE, invdrop = TRUE, turf/newloc = null)
|
|
if(to_drop && (organs.Find(to_drop) || bodyparts.Find(to_drop))) //let's not do this, aight?
|
|
return FALSE
|
|
return ..()
|
|
|
|
/// Helper to cleanly trigger tail wagging
|
|
/// Accepts an optional timeout after which we remove the tail wagging
|
|
/// Returns true if successful, false otherwise
|
|
/mob/living/carbon/proc/wag_tail(timeout = INFINITY)
|
|
var/obj/item/organ/tail/wagged = get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL)
|
|
if(!wagged)
|
|
return FALSE
|
|
return wagged.start_wag(src, timeout)
|
|
|
|
/// Helper to cleanly stop all tail wagging
|
|
/// Returns true if successful, false otherwise
|
|
/mob/living/carbon/proc/unwag_tail() // can't unwag a tail
|
|
var/obj/item/organ/tail/unwagged = get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL)
|
|
if(!unwagged)
|
|
return FALSE
|
|
return unwagged.stop_wag(src)
|
|
|
|
/mob/living/carbon/itch(obj/item/bodypart/target_part = null, damage = 0.5, can_scratch = TRUE, silent = FALSE)
|
|
if (isnull(target_part))
|
|
target_part = get_bodypart(get_random_valid_zone(even_weights = TRUE))
|
|
if (!IS_ORGANIC_LIMB(target_part) || (target_part.bodypart_flags & BODYPART_PSEUDOPART))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/mob/living/carbon/ominous_nosebleed()
|
|
var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
|
|
if(isnull(head))
|
|
return ..()
|
|
if(!can_bleed())
|
|
to_chat(src, span_notice("You get a headache."))
|
|
return
|
|
head.adjustBleedStacks(5)
|
|
visible_message(span_notice("[src] gets a nosebleed."), span_warning("You get a nosebleed."))
|
|
|
|
/mob/living/carbon/check_hit_limb_zone_name(hit_zone)
|
|
if(get_bodypart(hit_zone))
|
|
return hit_zone
|
|
// When a limb is missing the damage is actually passed to the chest
|
|
return BODY_ZONE_CHEST
|
|
|
|
/mob/living/carbon/get_bloodtype()
|
|
RETURN_TYPE(/datum/blood_type)
|
|
return dna?.blood_type
|