Files
Bubberstation/code/modules/mob/living/living.dm
grungussuss 613581897e /obj/item/clothing/head/mob_holder -> /obj/item/mob_holder (#91893)
## About The Pull Request
title
## Why It's Good For The Game
it inherits a lot of behavior from clothing which leads to lots of bugs
when it isn't actually clothes, for example: shredded overlays. this
should be better.
probably fixes a bunch of bugs
## Changelog
🆑
code: mob holders are no longer subtypes of clothes, report any issues
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
2025-07-01 14:20:48 +01:00

2997 lines
109 KiB
Plaintext

/mob/living/Initialize(mapload)
. = ..()
if(initial_size != RESIZE_DEFAULT_SIZE)
update_transform(initial_size)
AddElement(/datum/element/movetype_handler)
register_init_signals()
if(unique_name)
set_name()
var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
medhud.add_atom_to_hud(src)
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diag_hud.add_atom_to_hud(src)
faction += "[REF(src)]"
GLOB.mob_living_list += src
SSpoints_of_interest.make_point_of_interest(src)
update_fov()
gravity_setup()
ADD_TRAIT(src, TRAIT_UNIQUE_IMMERSE, INNATE_TRAIT)
/mob/living/prepare_huds()
..()
prepare_data_huds()
/mob/living/proc/prepare_data_huds()
med_hud_set_health()
med_hud_set_status()
/mob/living/Destroy()
for(var/datum/status_effect/effect as anything in status_effects)
// The status effect calls on_remove when its mob is deleted
if(effect.on_remove_on_mob_delete)
qdel(effect)
else
effect.be_replaced()
if(buckled)
buckled.unbuckle_mob(src,force=1)
remove_from_all_data_huds()
GLOB.mob_living_list -= src
if(imaginary_group)
imaginary_group -= src
QDEL_LIST(imaginary_group)
QDEL_LAZYLIST(diseases)
QDEL_LIST(surgeries)
QDEL_LIST(quirks)
return ..()
/mob/living/onZImpact(turf/impacted_turf, levels, impact_flags = NONE)
if(!isgroundlessturf(impacted_turf))
impact_flags |= ZImpactDamage(impacted_turf, levels)
return ..()
/**
* Called when this mob is receiving damage from falling
*
* * impacted_turf - the turf we are falling onto
* * levels - the number of levels we are falling
*/
/mob/living/proc/ZImpactDamage(turf/impacted_turf, levels)
. = SEND_SIGNAL(src, COMSIG_LIVING_Z_IMPACT, levels, impacted_turf)
if(. & ZIMPACT_CANCEL_DAMAGE)
return .
// multiplier for the damage taken from falling
var/damage_softening_multiplier = 1
var/obj/item/organ/cyberimp/chest/spine/potential_spine = get_organ_slot(ORGAN_SLOT_SPINE)
if(istype(potential_spine))
damage_softening_multiplier *= potential_spine.athletics_boost_multiplier
// If you are incapped, you probably can't brace yourself
var/can_help_themselves = !INCAPACITATED_IGNORING(src, INCAPABLE_RESTRAINTS)
if(levels <= 1 && can_help_themselves)
var/obj/item/organ/wings/gliders = get_organ_by_type(/obj/item/organ/wings)
if(HAS_TRAIT(src, TRAIT_FREERUNNING) || gliders?.can_soften_fall()) // the power of parkour or wings allows falling short distances unscathed
var/graceful_landing = HAS_TRAIT(src, TRAIT_CATLIKE_GRACE)
if(graceful_landing)
add_movespeed_modifier(/datum/movespeed_modifier/landed_on_feet)
addtimer(CALLBACK(src, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/landed_on_feet), levels * 3 SECONDS)
else
Knockdown(levels * 4 SECONDS)
emote("spin")
visible_message(
span_notice("[src] makes a hard landing on [impacted_turf] but remains unharmed from the fall[graceful_landing ? " and stays on [p_their()] feet" : " by tucking in rolling into the landing"]."),
span_notice("You brace for the fall. You make a hard landing on [impacted_turf], but remain unharmed[graceful_landing ? " while landing on your feet" : " by tucking in and rolling into the landing"]."),
)
return . | ZIMPACT_NO_MESSAGE
var/incoming_damage = (levels * 5) ** 1.5
// Smaller mobs with catlike grace can ignore damage (EG: cats)
var/small_surface_area = mob_size <= MOB_SIZE_SMALL
var/skip_knockdown = FALSE
if(HAS_TRAIT(src, TRAIT_CATLIKE_GRACE) && (small_surface_area || usable_legs >= 2) && body_position == STANDING_UP && can_help_themselves)
. |= ZIMPACT_NO_MESSAGE|ZIMPACT_NO_SPIN
skip_knockdown = TRUE
if(small_surface_area)
visible_message(
span_notice("[src] makes a hard landing on [impacted_turf], but lands safely on [p_their()] feet!"),
span_notice("You make a hard landing on [impacted_turf], but land safely on your feet!"),
)
new /obj/effect/temp_visual/mook_dust/small(impacted_turf)
return .
incoming_damage *= 1.66
add_movespeed_modifier(/datum/movespeed_modifier/landed_on_feet)
addtimer(CALLBACK(src, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/landed_on_feet), levels * 2 SECONDS)
visible_message(
span_danger("[src] makes a hard landing on [impacted_turf], landing on [p_their()] feet painfully!"),
span_userdanger("You make a hard landing on [impacted_turf], and instinctively land on your feet - painfully!"),
)
new /obj/effect/temp_visual/mook_dust(impacted_turf)
if(body_position == STANDING_UP)
var/damage_for_each_leg = round((incoming_damage / 2) * damage_softening_multiplier)
apply_damage(damage_for_each_leg, BRUTE, BODY_ZONE_L_LEG, wound_bonus = -2.5 * levels)
apply_damage(damage_for_each_leg, BRUTE, BODY_ZONE_R_LEG, wound_bonus = -2.5 * levels)
else
apply_damage(incoming_damage, BRUTE, spread_damage = TRUE)
if(!skip_knockdown)
Knockdown(levels * 5 SECONDS)
return .
/// Modifier for mobs landing on their feet after a fall
/datum/movespeed_modifier/landed_on_feet
movetypes = GROUND|UPSIDE_DOWN
multiplicative_slowdown = CRAWLING_ADD_SLOWDOWN / 2
//Generic Bump(). Override MobBump() and ObjBump() instead of this.
/mob/living/Bump(atom/A)
if(..()) //we are thrown onto something
return
if(buckled || now_pushing)
return
if(ismob(A))
var/mob/M = A
if(MobBump(M))
return
if(isobj(A))
var/obj/O = A
if(ObjBump(O))
return
if(ismovable(A))
var/atom/movable/AM = A
if(PushAM(AM, move_force))
return
/mob/living/Bumped(atom/movable/AM)
..()
last_bumped = world.time
//Called when we bump onto a mob
/mob/living/proc/MobBump(mob/M)
//No bumping/swapping/pushing others if you are on walk intent
if(move_intent == MOVE_INTENT_WALK)
return TRUE
if(SEND_SIGNAL(M, COMSIG_LIVING_PRE_MOB_BUMP, src) & COMPONENT_LIVING_BLOCK_PRE_MOB_BUMP)
return TRUE
SEND_SIGNAL(src, COMSIG_LIVING_MOB_BUMP, M)
SEND_SIGNAL(M, COMSIG_LIVING_MOB_BUMPED, src)
//Even if we don't push/swap places, we "touched" them, so spread fire
spreadFire(M)
if(now_pushing)
return TRUE
if(isliving(M))
var/mob/living/L = M
//Also spread diseases
for(var/thing in diseases)
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
L.ContactContractDisease(D)
for(var/thing in L.diseases)
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
ContactContractDisease(D)
//Should stop you pushing a restrained person out of the way
if(L.pulledby && L.pulledby != src && HAS_TRAIT(L, TRAIT_RESTRAINED))
if(!(world.time % 5))
to_chat(src, span_warning("[L] is restrained, you cannot push past."))
return TRUE
if(L.pulling)
if(ismob(L.pulling))
var/mob/P = L.pulling
if(HAS_TRAIT(P, TRAIT_RESTRAINED))
if(!(world.time % 5))
to_chat(src, span_warning("[L] is restraining [P], you cannot push past."))
return TRUE
if(moving_diagonally)//no mob swap during diagonal moves.
return TRUE
if(!M.buckled && !M.has_buckled_mobs())
if(can_mobswap_with(M))
//switch our position with M
if(loc && !loc.Adjacent(M.loc))
return TRUE
now_pushing = TRUE
var/oldloc = loc
var/oldMloc = M.loc
var/M_passmob = (M.pass_flags & PASSMOB) // we give PASSMOB to both mobs to avoid bumping other mobs during swap.
var/src_passmob = (pass_flags & PASSMOB)
M.pass_flags |= PASSMOB
pass_flags |= PASSMOB
var/move_failed = FALSE
if(!M.Move(oldloc) || !Move(oldMloc))
M.forceMove(oldMloc)
forceMove(oldloc)
move_failed = TRUE
if(!src_passmob)
pass_flags &= ~PASSMOB
if(!M_passmob)
M.pass_flags &= ~PASSMOB
now_pushing = FALSE
if(!move_failed)
return TRUE
//okay, so we didn't switch. but should we push?
//not if he's not CANPUSH of course
if(!(M.status_flags & CANPUSH))
return TRUE
if(isliving(M))
var/mob/living/L = M
if(HAS_TRAIT(L, TRAIT_PUSHIMMUNE))
return TRUE
//If they're a human, and they're not in help intent, block pushing
if(ishuman(M))
var/mob/living/carbon/human/human = M
if(human.combat_mode)
return TRUE
//if they are a cyborg, and they're alive and in combat mode, block pushing
if(iscyborg(M))
var/mob/living/silicon/robot/borg = M
if(borg.combat_mode && borg.stat != DEAD)
return TRUE
//anti-riot equipment is also anti-push
for(var/obj/item/I in M.held_items)
if(!isclothing(M))
if(prob(I.block_chance*2))
return
/mob/living/proc/can_mobswap_with(mob/other)
if (HAS_TRAIT(other, TRAIT_NOMOBSWAP) || HAS_TRAIT(src, TRAIT_NOMOBSWAP))
return FALSE
var/they_can_move = TRUE
var/their_combat_mode = FALSE
if(isliving(other))
var/mob/living/other_living = other
their_combat_mode = other_living.combat_mode
they_can_move = other_living.mobility_flags & MOBILITY_MOVE
var/too_strong = other.move_resist > move_force
// They cannot move, see if we can push through them
if (!they_can_move)
return !too_strong
// We are pulling them and can move through
if (other.pulledby == src && !too_strong)
return TRUE
// If we're in combat mode and not restrained we don't try to pass through people
if (combat_mode && !HAS_TRAIT(src, TRAIT_RESTRAINED))
return FALSE
// Nor can we pass through non-restrained people in combat mode (or if they're restrained but still too strong for us)
if (their_combat_mode && (!HAS_TRAIT(other, TRAIT_RESTRAINED) || too_strong))
return FALSE
if (isnull(other.client) || isnull(client))
return TRUE
// If both of us are trying to move in the same direction, let the fastest one through first
if (client.intended_direction == other.client.intended_direction)
return cached_multiplicative_slowdown < other.cached_multiplicative_slowdown
// Else, sure, let us pass
return TRUE
/mob/living/get_photo_description(obj/item/camera/camera)
var/list/holding = list()
var/len = length(held_items)
if(len)
for(var/obj/item/held_item in held_items)
if(!holding.len)
holding += "[p_They()] [p_are()] holding \a [held_item]"
else if(held_items.Find(held_item) == len)
holding += ", and \a [held_item]"
else
holding += ", \a [held_item]"
return "You can also see [src] on the photo[health < (maxHealth * 0.75) ? ", looking a bit hurt":""][holding.len ? ". [holding.Join("")].":"."]"
//Called when we bump onto an obj
/mob/living/proc/ObjBump(obj/O)
return
//Called when we want to push an atom/movable
/mob/living/proc/PushAM(atom/movable/AM, force = move_force)
if(now_pushing)
return TRUE
if(moving_diagonally)// no pushing during diagonal moves.
return TRUE
if(!client && (mob_size < MOB_SIZE_SMALL))
return
if(SEND_SIGNAL(AM, COMSIG_MOVABLE_BUMP_PUSHED, src, force) & COMPONENT_NO_PUSH)
return
now_pushing = TRUE
SEND_SIGNAL(src, COMSIG_LIVING_PUSHING_MOVABLE, AM)
var/dir_to_target = get_dir(src, AM)
// If there's no dir_to_target then the player is on the same turf as the atom they're trying to push.
// This can happen when a player is stood on the same turf as a directional window. All attempts to push
// the window will fail as get_dir will return 0 and the player will be unable to move the window when
// it should be pushable.
// In this scenario, we will use the facing direction of the /mob/living attempting to push the atom as
// a fallback.
if(!dir_to_target)
dir_to_target = dir
var/push_anchored = FALSE
if((AM.move_resist * MOVE_FORCE_CRUSH_RATIO) <= force)
if(move_crush(AM, move_force, dir_to_target))
push_anchored = TRUE
if((AM.move_resist * MOVE_FORCE_FORCEPUSH_RATIO) <= force) //trigger move_crush and/or force_push regardless of if we can push it normally
if(force_push(AM, move_force, dir_to_target, push_anchored))
push_anchored = TRUE
if(ismob(AM))
var/mob/mob_to_push = AM
var/atom/movable/mob_buckle = mob_to_push.buckled
// If we can't pull them because of what they're buckled to, make sure we can push the thing they're buckled to instead.
// If neither are true, we're not pushing anymore.
if(mob_buckle && (mob_buckle.buckle_prevents_pull || (force < (mob_buckle.move_resist * MOVE_FORCE_PUSH_RATIO))))
now_pushing = FALSE
return
if((AM.anchored && !push_anchored) || (force < (AM.move_resist * MOVE_FORCE_PUSH_RATIO)))
now_pushing = FALSE
return
if(istype(AM, /obj/structure/window))
var/obj/structure/window/W = AM
if(W.fulltile)
for(var/obj/structure/window/win in get_step(W, dir_to_target))
now_pushing = FALSE
return
if(pulling == AM)
stop_pulling()
var/current_dir
if(isliving(AM))
current_dir = AM.dir
if(AM.Move(get_step(AM.loc, dir_to_target), dir_to_target, glide_size))
AM.add_fingerprint(src)
Move(get_step(loc, dir_to_target), dir_to_target)
if(current_dir)
AM.setDir(current_dir)
now_pushing = FALSE
/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE)
if(!AM || !src)
return FALSE
if(!(AM.can_be_pulled(src, force)))
return FALSE
if(throwing || !(mobility_flags & MOBILITY_PULL))
return FALSE
if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_PULL, AM, force) & COMSIG_LIVING_CANCEL_PULL)
return FALSE
if(SEND_SIGNAL(AM, COMSIG_LIVING_TRYING_TO_PULL, src, force) & COMSIG_LIVING_CANCEL_PULL)
return FALSE
AM.add_fingerprint(src)
// If we're pulling something then drop what we're currently pulling and pull this instead.
if(pulling)
// Are we trying to pull something we are already pulling? Then just stop here, no need to continue.
if(AM == pulling)
return FALSE
stop_pulling()
changeNext_move(CLICK_CD_GRABBING)
if(AM.pulledby)
if(!supress_message)
AM.visible_message(span_danger("[src] pulls [AM] from [AM.pulledby]'s grip."), \
span_danger("[src] pulls you from [AM.pulledby]'s grip."), null, null, src)
to_chat(src, span_notice("You pull [AM] from [AM.pulledby]'s grip!"))
log_combat(AM, AM.pulledby, "pulled from", src)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = AM
AM.set_pulledby(src)
SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force)
if(!supress_message)
var/sound_to_play = 'sound/items/weapons/thudswoosh.ogg'
if(ishuman(src))
var/mob/living/carbon/human/H = src
if(H.dna.species.grab_sound)
sound_to_play = H.dna.species.grab_sound
if(HAS_TRAIT(H, TRAIT_STRONG_GRABBER))
sound_to_play = null
playsound(src.loc, sound_to_play, 50, TRUE, -1)
update_pull_hud_icon()
if(ismob(AM))
var/mob/M = AM
log_combat(src, M, "grabbed", addition="passive grab")
if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER)))
if(ishuman(M))
var/mob/living/carbon/human/grabbed_human = M
var/grabbed_by_hands = (zone_selected == "l_arm" || zone_selected == "r_arm") && grabbed_human.usable_hands > 0
M.visible_message(span_warning("[src] grabs [M] [grabbed_by_hands ? "by their hands":"passively"]!"), \
span_warning("[src] grabs you [grabbed_by_hands ? "by your hands":"passively"]!"), null, null, src)
to_chat(src, span_notice("You grab [M] [grabbed_by_hands ? "by their hands":"passively"]!"))
grabbed_human.share_blood_on_touch(src, grabbed_by_hands ? ITEM_SLOT_GLOVES : ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)
else
M.visible_message(span_warning("[src] grabs [M] passively!"), \
span_warning("[src] grabs you passively!"), null, null, src)
to_chat(src, span_notice("You grab [M] passively!"))
if(isliving(M))
var/mob/living/L = M
SEND_SIGNAL(M, COMSIG_LIVING_GET_PULLED, src)
//Share diseases that are spread by touch
for(var/thing in diseases)
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
L.ContactContractDisease(D)
for(var/thing in L.diseases)
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
ContactContractDisease(D)
if(iscarbon(L))
var/mob/living/carbon/C = L
if(HAS_TRAIT(src, TRAIT_STRONG_GRABBER))
C.grabbedby(src)
update_pull_movespeed()
set_pull_offsets(M, state)
return TRUE
/**
* Updates the offsets of the passed mob according to the passed grab state and the direction between them and us
*
* * M - the mob to update the offsets of
* * grab_state - the state of the grab
* * animate - whether or not to animate the offsets
*/
/mob/living/proc/set_pull_offsets(mob/living/mob_to_set, grab_state = GRAB_PASSIVE, animate = TRUE)
if(mob_to_set.buckled)
return //don't make them change direction or offset them if they're buckled into something.
var/offset = 0
switch(grab_state)
if(GRAB_PASSIVE)
offset = GRAB_PIXEL_SHIFT_PASSIVE
if(GRAB_AGGRESSIVE)
offset = GRAB_PIXEL_SHIFT_AGGRESSIVE
if(GRAB_NECK)
offset = GRAB_PIXEL_SHIFT_NECK
if(GRAB_KILL)
offset = GRAB_PIXEL_SHIFT_NECK
mob_to_set.setDir(get_dir(mob_to_set, src))
var/dir_filter = mob_to_set.dir
if(ISDIAGONALDIR(dir_filter))
dir_filter = EWCOMPONENT(dir_filter)
switch(dir_filter)
if(NORTH)
mob_to_set.add_offsets(GRABBING_TRAIT, x_add = 0, y_add = offset, animate = animate)
if(SOUTH)
mob_to_set.add_offsets(GRABBING_TRAIT, x_add = 0, y_add = -offset, animate = animate)
if(EAST)
if(mob_to_set.lying_angle == LYING_ANGLE_WEST) //update the dragged dude's direction if we've turned
mob_to_set.set_lying_angle(LYING_ANGLE_EAST)
mob_to_set.add_offsets(GRABBING_TRAIT, x_add = offset, y_add = 0, animate = animate)
if(WEST)
if(mob_to_set.lying_angle == LYING_ANGLE_EAST)
mob_to_set.set_lying_angle(LYING_ANGLE_WEST)
mob_to_set.add_offsets(GRABBING_TRAIT, x_add = -offset, y_add = 0, animate = animate)
/**
* Removes any offsets from the passed mob that are related to being grabbed
*
* * M - the mob to remove the offsets from
* * override - if TRUE, the offsets will be removed regardless of the mob's buckled state
* otherwise we won't remove the offsets if the mob is buckled
*/
/mob/living/proc/reset_pull_offsets(mob/living/M, override)
if(!override && M.buckled)
return
M.remove_offsets(GRABBING_TRAIT)
//mob verbs are a lot faster than object verbs
//for more info on why this is not atom/pull, see examinate() in mob.dm
/mob/living/verb/pulled(atom/movable/AM as mob|obj in oview(1))
set name = "Pull"
set category = "Object"
if(istype(AM) && Adjacent(AM))
start_pulling(AM)
else if(!combat_mode) //Don;'t cancel pulls if misclicking in combat mode.
stop_pulling()
/mob/living/stop_pulling()
if(ismob(pulling))
reset_pull_offsets(pulling)
..()
update_pull_movespeed()
update_pull_hud_icon()
/mob/living/verb/stop_pulling1()
set name = "Stop Pulling"
set category = "IC"
stop_pulling()
//same as above
/mob/living/pointed(atom/A as mob|obj|turf in view(client.view, src))
if(INCAPACITATED_IGNORING(src, INCAPABLE_RESTRAINTS))
return FALSE
return ..()
/mob/living/_pointed(atom/pointing_at)
if(!..())
return FALSE
log_message("points at [pointing_at]", LOG_EMOTE)
visible_message(span_infoplain("[span_name("[src]")] points at [pointing_at]."), span_notice("You point at [pointing_at]."))
/mob/living/verb/succumb(whispered as num|null)
set hidden = TRUE
if (!CAN_SUCCUMB(src))
if(HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE))
if(whispered)
to_chat(src, span_notice("Your immortal body is keeping you alive! Unless you just press the UI button."), type=MESSAGE_TYPE_INFO)
return
else
to_chat(src, span_warning("You are unable to succumb to death! This life continues."), type=MESSAGE_TYPE_INFO)
return
log_message("Has [whispered ? "whispered his final words" : "succumbed to death"] with [round(health, 0.1)] points of health!", LOG_ATTACK)
adjustOxyLoss(health - HEALTH_THRESHOLD_DEAD)
updatehealth()
if(!whispered)
to_chat(src, span_notice("You have given up life and succumbed to death."))
investigate_log("has succumbed to death.", INVESTIGATE_DEATHS)
death()
// Remember, anything that influences this needs to call update_incapacitated somehow when it changes
// Most often best done in [code/modules/mob/living/init_signals.dm]
/mob/living/build_incapacitated(flags)
// Holds a set of flags that describe how we are currently incapacitated
var/incap_status = NONE
if(HAS_TRAIT(src, TRAIT_INCAPACITATED))
incap_status |= TRADITIONAL_INCAPACITATED
if(HAS_TRAIT(src, TRAIT_RESTRAINED))
incap_status |= INCAPABLE_RESTRAINTS
if(pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)
incap_status |= INCAPABLE_GRAB
if(HAS_TRAIT(src, TRAIT_STASIS))
incap_status |= INCAPABLE_STASIS
return incap_status
/mob/living/canUseStorage()
if (usable_hands <= 0)
return FALSE
return TRUE
//This proc is used for mobs which are affected by pressure to calculate the amount of pressure that actually
//affects them once clothing is factored in. ~Errorage
/mob/living/proc/calculate_affecting_pressure(pressure)
return pressure
/mob/living/proc/getMaxHealth()
return maxHealth
/mob/living/proc/setMaxHealth(newMaxHealth)
maxHealth = newMaxHealth
/// Returns the health of the mob while ignoring damage of non-organic (prosthetic) limbs
/// Used by cryo cells to not permanently imprison those with damage from prosthetics,
/// as they cannot be healed through chemicals.
/mob/living/proc/get_organic_health()
return health
// MOB PROCS //END
/mob/living/proc/mob_sleep()
set name = "Sleep"
set category = "IC"
if(IsSleeping())
to_chat(src, span_warning("You are already sleeping!"))
return
else
if(tgui_alert(usr, "You sure you want to sleep for a while?", "Sleep", list("Yes", "No")) == "Yes")
SetSleeping(400) //Short nap
/mob/proc/get_contents()
/**
* Gets ID card from a mob.
* Argument:
* * hand_firsts - boolean that checks the hands of the mob first if TRUE.
*/
/mob/living/proc/get_idcard(hand_first)
if(!length(held_items)) //Early return for mobs without hands.
return
//Check hands
var/obj/item/held_item = get_active_held_item()
if(held_item) //Check active hand
. = held_item.GetID()
if(!.) //If there is no id, check the other hand
held_item = get_inactive_held_item()
if(held_item)
. = held_item.GetID()
/**
* Returns the access list for this mob
*/
/mob/living/proc/get_access()
var/list/access_list = list()
SEND_SIGNAL(src, COMSIG_MOB_RETRIEVE_SIMPLE_ACCESS, access_list)
var/obj/item/card/id/id = get_idcard()
access_list += id?.GetAccess()
return access_list
/mob/living/proc/get_id_in_hand()
var/obj/item/held_item = get_active_held_item()
if(!held_item)
return
return held_item.GetID()
//Returns the bank account of an ID the user may be holding.
/mob/living/proc/get_bank_account()
RETURN_TYPE(/datum/bank_account)
var/datum/bank_account/account
var/obj/item/card/id/I = get_idcard()
if(I?.registered_account)
account = I.registered_account
return account
/mob/living/proc/toggle_resting()
set name = "Rest"
set category = "IC"
set_resting(!resting, FALSE)
///Proc to hook behavior to the change of value in the resting variable.
/mob/living/proc/set_resting(new_resting, silent = TRUE, instant = FALSE)
if(!(mobility_flags & MOBILITY_REST))
return
if(new_resting == resting)
return
. = resting
resting = new_resting
if(new_resting)
if(body_position == LYING_DOWN)
if(!silent)
to_chat(src, span_notice("You will now try to stay lying down on the floor."))
else if(HAS_TRAIT(src, TRAIT_FORCED_STANDING) || (buckled && buckled.buckle_lying != NO_BUCKLE_LYING))
if(!silent)
to_chat(src, span_notice("You will now lay down as soon as you are able to."))
else
if(!silent)
to_chat(src, span_notice("You lay down."))
set_lying_down()
else
if(body_position == STANDING_UP)
if(!silent)
to_chat(src, span_notice("You will now try to remain standing up."))
else if(HAS_TRAIT(src, TRAIT_FLOORED) || (buckled && buckled.buckle_lying != NO_BUCKLE_LYING))
if(!silent)
to_chat(src, span_notice("You will now stand up as soon as you are able to."))
else
if(!silent)
to_chat(src, span_notice("You stand up."))
get_up(instant)
SEND_SIGNAL(src, COMSIG_LIVING_RESTING, new_resting, silent, instant)
update_resting()
/// Proc to append and redefine behavior to the change of the [/mob/living/var/resting] variable.
/mob/living/proc/update_resting()
update_rest_hud_icon()
/mob/living/proc/get_up(instant = FALSE)
set waitfor = FALSE
var/get_up_time = 1 SECONDS
var/obj/item/organ/cyberimp/chest/spine/potential_spine = get_organ_slot(ORGAN_SLOT_SPINE)
if(istype(potential_spine))
get_up_time *= potential_spine.athletics_boost_multiplier
if(!instant && !do_after(src, get_up_time, src, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM), extra_checks = CALLBACK(src, TYPE_PROC_REF(/mob/living, rest_checks_callback)), interaction_key = DOAFTER_SOURCE_GETTING_UP, hidden = TRUE))
return
if(resting || body_position == STANDING_UP || HAS_TRAIT(src, TRAIT_FLOORED))
return
set_body_position(STANDING_UP)
set_lying_angle(0)
/mob/living/proc/rest_checks_callback()
if(resting || body_position == STANDING_UP || HAS_TRAIT(src, TRAIT_FLOORED))
return FALSE
return TRUE
/// Change the [body_position] to [LYING_DOWN] and update associated behavior.
/mob/living/proc/set_lying_down(new_lying_angle)
set_body_position(LYING_DOWN)
/// Proc to append behavior related to lying down.
/mob/living/proc/on_lying_down(new_lying_angle)
if(layer == initial(layer)) //to avoid things like hiding larvas.
layer = LYING_MOB_LAYER //so mob lying always appear behind standing mobs
add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED, TRAIT_UNDENSE), LYING_DOWN_TRAIT)
if(HAS_TRAIT(src, TRAIT_FLOORED) && !(dir & (NORTH|SOUTH)))
setDir(pick(NORTH, SOUTH)) // We are and look helpless.
if(rotate_on_lying)
add_offsets(LYING_DOWN_TRAIT, y_add = PIXEL_Y_OFFSET_LYING)
/// Proc to append behavior related to lying down.
/mob/living/proc/on_standing_up()
if(layer == LYING_MOB_LAYER)
layer = initial(layer)
remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED, TRAIT_UNDENSE), LYING_DOWN_TRAIT)
remove_offsets(LYING_DOWN_TRAIT)
/mob/living/proc/update_density()
if(HAS_TRAIT(src, TRAIT_UNDENSE))
set_density(FALSE)
else
set_density(TRUE)
/mob/living/update_rest_hud_icon()
. = ..()
if(!.)
return FALSE
if(!hud_used.sleep_icon || HAS_TRAIT(src, TRAIT_SLEEPIMMUNE))
return TRUE
if(resting || HAS_TRAIT(src, TRAIT_FLOORED))
hud_used.static_inventory |= hud_used.sleep_icon
else
hud_used.static_inventory -= hud_used.sleep_icon
hud_used.show_hud(hud_used.hud_version)
return TRUE
//Recursive function to find everything a mob is holding. Really shitty proc tbh.
/mob/living/get_contents()
var/list/ret = list()
ret |= contents //add our contents
for(var/atom/iter_atom as anything in ret) //iterate storage objects
ret |= iter_atom.atom_storage?.return_inv()
for(var/obj/item/folder/folder in ret) //very snowflakey-ly iterate folders
ret |= folder.contents
return ret
/**
* Returns whether or not the mob can be injected. Should not perform any side effects.
*
* Arguments:
* * user - The user trying to inject the mob.
* * target_zone - The zone being targeted.
* * injection_flags - A bitflag for extra properties to check.
* Check __DEFINES/injection.dm for more details, specifically the ones prefixed INJECT_CHECK_*.
*/
/mob/living/proc/can_inject(mob/user, target_zone, injection_flags)
return TRUE
/**
* Like can_inject, but it can perform side effects.
*
* Arguments:
* * user - The user trying to inject the mob.
* * target_zone - The zone being targeted.
* * injection_flags - A bitflag for extra properties to check. Check __DEFINES/injection.dm for more details.
* Check __DEFINES/injection.dm for more details. Unlike can_inject, the INJECT_TRY_* defines will behave differently.
*/
/mob/living/proc/try_inject(mob/user, target_zone, injection_flags)
return can_inject(user, target_zone, injection_flags)
/mob/living/is_injectable(mob/user, allowmobs = TRUE)
return (allowmobs && reagents && can_inject(user))
/mob/living/is_drawable(mob/user, allowmobs = TRUE)
return (allowmobs && reagents && can_inject(user))
///Sets the current mob's health value. Do not call directly if you don't know what you are doing, use the damage procs, instead.
/mob/living/proc/set_health(new_value)
. = health
health = new_value
/mob/living/proc/updatehealth()
if(HAS_TRAIT(src, TRAIT_GODMODE))
return
set_health(maxHealth - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss())
update_stat()
med_hud_set_health()
med_hud_set_status()
update_health_hud()
update_stamina()
SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE)
/mob/living/update_health_hud()
var/severity = 0
var/healthpercent = (health/maxHealth) * 100
if(hud_used?.healthdoll) //to really put you in the boots of a simplemob
var/atom/movable/screen/healthdoll/living/livingdoll = hud_used.healthdoll
switch(healthpercent)
if(100 to INFINITY)
severity = 0
if(80 to 100)
severity = 1
if(60 to 80)
severity = 2
if(40 to 60)
severity = 3
if(20 to 40)
severity = 4
if(1 to 20)
severity = 5
else
severity = 6
livingdoll.icon_state = "living[severity]"
if(!livingdoll.filtered)
livingdoll.filtered = TRUE
var/icon/mob_mask = icon(icon, icon_state)
if(get_cached_height() > ICON_SIZE_Y || get_cached_width() > ICON_SIZE_X)
var/health_doll_icon_state = health_doll_icon ? health_doll_icon : "megasprite"
mob_mask = icon('icons/hud/screen_gen.dmi', health_doll_icon_state) //swap to something generic if they have no special doll
livingdoll.add_filter("mob_shape_mask", 1, alpha_mask_filter(icon = mob_mask))
livingdoll.add_filter("inset_drop_shadow", 2, drop_shadow_filter(size = -1))
if(severity > 0)
overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity)
else
clear_fullscreen("brute")
/**
* Proc used to resuscitate a mob, bringing them back to life.
*
* Note that, even if a mob cannot be revived, the healing from this proc will still be applied.
*
* Arguments
* * full_heal_flags - Optional. If supplied, [/mob/living/fully_heal] will be called with these flags before revival.
* * excess_healing - Optional. If supplied, this number will be used to apply a bit of healing to the mob. Currently, 1 "excess healing" translates to -1 oxyloss, -1 toxloss, +2 blood, -5 to all organ damage.
* * force_grab_ghost - We grab the ghost of the mob on revive. If TRUE, we force grab the ghost (includes suiciders). If FALSE, we do not. See [/mob/grab_ghost].
*
*/
/mob/living/proc/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
if(QDELETED(src))
// Bro just like, don't ok
return FALSE
if(excess_healing)
adjustOxyLoss(-excess_healing, updating_health = FALSE)
adjustToxLoss(-excess_healing, updating_health = FALSE, forced = TRUE) //slime friendly
updatehealth()
grab_ghost(force_grab_ghost)
if(full_heal_flags)
fully_heal(full_heal_flags)
if(stat == DEAD && can_be_revived()) //in some cases you can't revive (e.g. no brain)
set_suicide(FALSE)
set_stat(UNCONSCIOUS) //the mob starts unconscious,
updatehealth() //then we check if the mob should wake up.
if(full_heal_flags & HEAL_ADMIN)
get_up(TRUE)
update_sight()
clear_alert(ALERT_NOT_ENOUGH_OXYGEN)
reload_fullscreen()
. = TRUE
if(excess_healing)
INVOKE_ASYNC(src, PROC_REF(emote), "gasp")
log_combat(src, src, "revived")
else if(full_heal_flags & HEAL_ADMIN)
updatehealth()
get_up(TRUE)
// The signal is called after everything else so components can properly check the updated values
SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, full_heal_flags)
/**
* Heals up the mob up to [heal_to] of the main damage types.
* EX: If heal_to is 50, and they have 150 brute damage, they will heal 100 brute (up to 50 brute damage)
*
* If the target is dead, also revives them and heals their organs / restores blood.
* If we have a [revive_message], play a visible message if the revive was successful.
*
* Arguments
* * heal_to - the health threshold to heal the mob up to for each of the main damage types.
* * revive_message - if provided, a visible message to show on a successful revive.
*
* Returns TRUE if the mob is alive afterwards, or FALSE if they're still dead (revive failed).
*/
/mob/living/proc/heal_and_revive(heal_to = 50, revive_message)
// Heal their brute and burn up to the threshold we're looking for
var/brute_to_heal = heal_to - getBruteLoss()
var/burn_to_heal = heal_to - getFireLoss()
var/oxy_to_heal = heal_to - getOxyLoss()
var/tox_to_heal = heal_to - getToxLoss()
if(brute_to_heal < 0)
adjustBruteLoss(brute_to_heal, updating_health = FALSE)
if(burn_to_heal < 0)
adjustFireLoss(burn_to_heal, updating_health = FALSE)
if(oxy_to_heal < 0)
adjustOxyLoss(oxy_to_heal, updating_health = FALSE)
if(tox_to_heal < 0)
adjustToxLoss(tox_to_heal, updating_health = FALSE, forced = TRUE)
// Run updatehealth once to set health for the revival check
updatehealth()
// We've given them a decent heal.
// If they happen to be dead too, try to revive them - if possible.
if(stat == DEAD && can_be_revived())
// If the revive is successful, show our revival message (if present).
if(revive(excess_healing = 10) && revive_message)
visible_message(revive_message)
// Finally update health again after we're all done
updatehealth()
return stat != DEAD
/**
* A grand proc used whenever this mob is, quote, "fully healed".
* Fully healed could mean a number of things, such as "healing all the main damage types", "healing all the organs", etc
* So, you can pass flags to specify
*
* See [mobs.dm] for more information on the flags
*
* If you ever think "hey I'm adding something and want it to be reverted on full heal",
* consider handling it via signal instead of implementing it in this proc
*/
/mob/living/proc/fully_heal(heal_flags = HEAL_ALL)
SHOULD_CALL_PARENT(TRUE)
if(heal_flags & HEAL_TOX)
setToxLoss(0, updating_health = FALSE, forced = TRUE)
if(heal_flags & HEAL_OXY)
setOxyLoss(0, updating_health = FALSE, forced = TRUE)
if(heal_flags & HEAL_BRUTE)
setBruteLoss(0, updating_health = FALSE, forced = TRUE)
if(heal_flags & HEAL_BURN)
setFireLoss(0, updating_health = FALSE, forced = TRUE)
if(heal_flags & HEAL_STAM)
setStaminaLoss(0, updating_stamina = FALSE, forced = TRUE)
// I don't really care to keep this under a flag
set_nutrition(NUTRITION_LEVEL_FED + 50)
overeatduration = 0
satiety = 0
// These should be tracked by status effects
losebreath = 0
set_disgust(0)
cure_husk()
if(heal_flags & HEAL_TEMP)
bodytemperature = get_body_temp_normal(apply_change = FALSE)
if(heal_flags & HEAL_BLOOD)
restore_blood()
if(reagents && (heal_flags & HEAL_ALL_REAGENTS))
reagents.clear_reagents()
if(heal_flags & HEAL_ADMIN)
REMOVE_TRAIT(src, TRAIT_SUICIDED, REF(src))
updatehealth()
stop_sound_channel(CHANNEL_HEARTBEAT)
SEND_SIGNAL(src, COMSIG_LIVING_POST_FULLY_HEAL, heal_flags)
/**
* Called by strange_reagent, with the amount of healing the strange reagent is doing
* It uses the healing amount on brute/fire damage, and then uses the excess healing for revive
*/
/mob/living/proc/do_strange_reagent_revival(healing_amount)
var/brute_loss = getBruteLoss()
if(brute_loss)
var/brute_healing = min(healing_amount * 0.5, brute_loss) // 50% of the healing goes to brute
setBruteLoss(round(brute_loss - brute_healing, DAMAGE_PRECISION), updating_health=FALSE, forced=TRUE)
healing_amount = max(0, healing_amount - brute_healing)
var/fire_loss = getFireLoss()
if(fire_loss && healing_amount)
var/fire_healing = min(healing_amount, fire_loss) // rest of the healing goes to fire
setFireLoss(round(fire_loss - fire_healing, DAMAGE_PRECISION), updating_health=TRUE, forced=TRUE)
healing_amount = max(0, healing_amount - fire_healing)
revive(NONE, excess_healing=max(healing_amount, 0), force_grab_ghost=FALSE) // and any excess healing is passed along
/// Checks if we are actually able to ressuscitate this mob.
/// (We don't want to revive then to have them instantly die again)
/mob/living/proc/can_be_revived()
if(health <= HEALTH_THRESHOLD_DEAD)
return FALSE
return TRUE
/mob/living/proc/update_damage_overlays()
return
/mob/living/Move(atom/newloc, direct, glide_size_override)
if(lying_angle != 0)
lying_angle_on_movement(direct)
if (buckled && buckled.loc != newloc) //not updating position
if (!buckled.anchored)
buckled.moving_from_pull = moving_from_pull
. = buckled.Move(newloc, direct, glide_size)
buckled.moving_from_pull = null
return
var/old_direction = dir
var/turf/old_loc = loc
if(pulling)
update_pull_movespeed()
. = ..()
if(moving_diagonally != FIRST_DIAG_STEP && isliving(pulledby))
var/mob/living/puller = pulledby
puller.set_pull_offsets(src, puller.grab_state)
if(active_storage)
var/storage_is_important_recurisve = (active_storage.parent in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE])
var/can_reach_active_storage = CanReach(active_storage.parent, view_only = TRUE)
if(!storage_is_important_recurisve && !can_reach_active_storage)
active_storage.hide_contents(src)
if(!buckled && !moving_diagonally && loc != old_loc)
var/blood_flow = get_bleed_rate()
var/health_check = body_position == LYING_DOWN && prob(getBruteLoss() * 200 / maxHealth)
var/bleeding_check = blood_flow > 3 && prob(blood_flow * 16)
if(health_check || bleeding_check)
make_blood_trail(newloc, old_loc, old_direction, direct)
///Called by mob Move() when the lying_angle is different than zero, to better visually simulate crawling.
/mob/living/proc/lying_angle_on_movement(direct)
if(direct & EAST)
set_lying_angle(LYING_ANGLE_EAST)
else if(direct & WEST)
set_lying_angle(LYING_ANGLE_WEST)
/mob/living/carbon/alien/adult/lying_angle_on_movement(direct)
return
/// Print a message about an annoying sensation you are feeling. Returns TRUE if successful.
/mob/living/proc/itch(obj/item/bodypart/target_part = null, damage = 0.5, can_scratch = TRUE, silent = FALSE)
if ((mob_biotypes & (MOB_ROBOTIC | MOB_SPIRIT)))
return FALSE
var/will_scratch = can_scratch && !incapacitated
var/applied_damage = 0
if (will_scratch && damage)
applied_damage = apply_damage(damage, damagetype = BRUTE, def_zone = target_part)
if (silent)
return applied_damage > 0
var/visible_part = isnull(target_part) ? "side" : target_part.plaintext_zone
visible_message("[can_scratch ? span_warning("[src] scratches [p_their()] [visible_part].") : ""]", span_warning("Your [visible_part] itches. [can_scratch ? "You scratch it." : ""]"))
return TRUE
/mob/living/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE)
if(buckled || mob_negates_gravity())
return
if(client && client.move_delay >= world.time + world.tick_lag*2)
pressure_resistance_prob_delta -= 30
var/list/turfs_to_check = list()
if(!has_limbs)
var/turf/T = get_step(src, angle2dir(dir2angle(direction)+90))
if (T)
turfs_to_check += T
T = get_step(src, angle2dir(dir2angle(direction)-90))
if(T)
turfs_to_check += T
for(var/t in turfs_to_check)
T = t
if(T.density)
pressure_resistance_prob_delta -= 20
continue
for (var/atom/movable/AM in T)
if (AM.density && AM.anchored)
pressure_resistance_prob_delta -= 20
break
..(pressure_difference, direction, pressure_resistance_prob_delta)
/mob/living/can_resist()
if(next_move > world.time)
return FALSE
if(HAS_TRAIT(src, TRAIT_INCAPACITATED))
return FALSE
return TRUE
/mob/living/verb/resist()
set name = "Resist"
set category = "IC"
DEFAULT_QUEUE_OR_CALL_VERB(VERB_CALLBACK(src, PROC_REF(execute_resist)))
///proc extender of [/mob/living/verb/resist] meant to make the process queable if the server is overloaded when the verb is called
/mob/living/proc/execute_resist()
if(!can_resist())
return
changeNext_move(CLICK_CD_RESIST)
SEND_SIGNAL(src, COMSIG_LIVING_RESIST, src)
//resisting grabs (as if it helps anyone...)
if(!HAS_TRAIT(src, TRAIT_RESTRAINED) && pulledby)
log_combat(src, pulledby, "resisted grab")
resist_grab()
return
//unbuckling yourself
if(buckled && last_special <= world.time)
resist_buckle()
//Breaking out of a container (Locker, sleeper, cryo...)
else if(loc != get_turf(src))
loc.container_resist_act(src)
else if(mobility_flags & MOBILITY_MOVE)
if(on_fire)
resist_fire() //stop, drop, and roll
else if(last_special <= world.time)
resist_restraints() //trying to remove cuffs.
/mob/proc/resist_grab(moving_resist)
return 1 //returning 0 means we successfully broke free
/mob/living/resist_grab(moving_resist)
. = TRUE
//Our effective grab state. GRAB_PASSIVE is equal to 0, so if we have no other altering factors to our grab state, we can break free immediately on resist.
var/effective_grab_state = pulledby.grab_state
//The amount of damage inflicted on a failed resist attempt.
var/damage_on_resist_fail = rand(7, 13)
if(body_position == LYING_DOWN) //If prone, treat the grab state as one higher
effective_grab_state++
if(HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) //If we have grab weakness from some source, treat the grab state as one higher
effective_grab_state++
if(get_timed_status_effect_duration(/datum/status_effect/staggered) && (getFireLoss() + getBruteLoss()) >= 40) //If we are staggered, and we have at least 40 damage, treat the grab state as one higher.
effective_grab_state++
if(HAS_TRAIT(src, TRAIT_GRABRESISTANCE)) //If we have grab resistance from some source, treat the grab state as one lower.
effective_grab_state--
//If our puller is a human, and they have an active hand they're grabbing with (please don't ask how people grab without hands), then apply their unarmed values to the grab values
if(pulledby && ishuman(pulledby))
var/mob/living/carbon/human/human_puller = pulledby
var/obj/item/bodypart/grabbing_bodypart = human_puller.get_active_hand()
if(grabbing_bodypart)
damage_on_resist_fail += rand(grabbing_bodypart.unarmed_damage_low, grabbing_bodypart.unarmed_damage_high)
//If our puller is a drunken brawler, they add more damage based on their own damage taken so long as they're drunk and treat the grab state as one higher
var/puller_drunkenness = human_puller.get_drunk_amount()
if(puller_drunkenness && HAS_TRAIT(human_puller, TRAIT_DRUNKEN_BRAWLER))
damage_on_resist_fail += clamp((human_puller.getFireLoss() + human_puller.getBruteLoss()) / 10, 3, 20)
effective_grab_state ++
//We only resist our grab state if we are currently in a grab equal to or greater than GRAB_AGGRESSIVE (1). Otherwise, break out immediately!
if(effective_grab_state >= GRAB_AGGRESSIVE)
// see defines/combat.dm, this should be baseline 60%
// Resist chance divided by the value imparted by your grab state. It isn't until you reach neckgrab that you gain a penalty to escaping a grab.
var/resist_chance = clamp(BASE_GRAB_RESIST_CHANCE / effective_grab_state, 0, 100)
if(prob(resist_chance))
visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \
span_danger("You break free of [pulledby]'s grip!"), null, null, pulledby)
to_chat(pulledby, span_warning("[src] breaks free of your grip!"))
log_combat(pulledby, src, "broke grab")
pulledby.stop_pulling()
return FALSE
else
adjustStaminaLoss(damage_on_resist_fail) //Do some stamina damage if we fail to resist
visible_message(span_danger("[src] struggles as they fail to break free of [pulledby]'s grip!"), \
span_warning("You struggle as you fail to break free of [pulledby]'s grip!"), null, null, pulledby)
to_chat(pulledby, span_danger("[src] struggles as they fail to break free of your grip!"))
if(moving_resist && client) //we resisted by trying to move
client.move_delay = world.time + 4 SECONDS
else
pulledby.stop_pulling()
return FALSE
/mob/living/proc/resist_buckle()
buckled.user_unbuckle_mob(src,src)
/mob/living/proc/resist_fire()
return FALSE
/mob/living/proc/resist_restraints()
return
/mob/living/proc/update_gravity(gravity)
// Handle movespeed stuff
var/speed_change = max(0, gravity - STANDARD_GRAVITY)
if(speed_change)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/gravity, multiplicative_slowdown=speed_change)
else
remove_movespeed_modifier(/datum/movespeed_modifier/gravity)
// Time to add/remove gravity alerts. sorry for the mess it's gotta be fast
var/atom/movable/screen/alert/gravity_alert = alerts[ALERT_GRAVITY]
switch(gravity)
if(-INFINITY to NEGATIVE_GRAVITY)
if(!istype(gravity_alert, /atom/movable/screen/alert/negative))
throw_alert(ALERT_GRAVITY, /atom/movable/screen/alert/negative)
ADD_TRAIT(src, TRAIT_MOVE_UPSIDE_DOWN, NEGATIVE_GRAVITY_TRAIT)
var/matrix/flipped_matrix = transform
flipped_matrix.b = -flipped_matrix.b
flipped_matrix.e = -flipped_matrix.e
animate(src, transform = flipped_matrix, time = 0.5 SECONDS, easing = EASE_OUT, flags = ANIMATION_PARALLEL)
add_offsets(NEGATIVE_GRAVITY_TRAIT, y_add = 4)
if(NEGATIVE_GRAVITY + 0.01 to 0)
if(!istype(gravity_alert, /atom/movable/screen/alert/weightless))
throw_alert(ALERT_GRAVITY, /atom/movable/screen/alert/weightless)
ADD_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT)
if(0.01 to STANDARD_GRAVITY)
if(gravity_alert)
clear_alert(ALERT_GRAVITY)
if(STANDARD_GRAVITY + 0.01 to GRAVITY_DAMAGE_THRESHOLD - 0.01)
throw_alert(ALERT_GRAVITY, /atom/movable/screen/alert/highgravity)
if(GRAVITY_DAMAGE_THRESHOLD to INFINITY)
throw_alert(ALERT_GRAVITY, /atom/movable/screen/alert/veryhighgravity)
// If we had no gravity alert, or the same alert as before, go home
if(!gravity_alert || alerts[ALERT_GRAVITY] == gravity_alert)
return
// By this point we know that we do not have the same alert as we used to
if(istype(gravity_alert, /atom/movable/screen/alert/weightless))
REMOVE_TRAIT(src, TRAIT_MOVE_FLOATING, NO_GRAVITY_TRAIT)
if(istype(gravity_alert, /atom/movable/screen/alert/negative))
REMOVE_TRAIT(src, TRAIT_MOVE_UPSIDE_DOWN, NEGATIVE_GRAVITY_TRAIT)
var/matrix/flipped_matrix = transform
flipped_matrix.b = -flipped_matrix.b
flipped_matrix.e = -flipped_matrix.e
animate(src, transform = flipped_matrix, time = 0.5 SECONDS, easing = EASE_OUT, flags = ANIMATION_PARALLEL)
remove_offsets(NEGATIVE_GRAVITY_TRAIT)
/mob/living/singularity_pull(atom/singularity, current_size)
..()
if(move_resist == INFINITY)
return
if(current_size >= STAGE_SIX) //your puny magboots/wings/whatever will not save you against supermatter singularity
throw_at(singularity, 14, 3, src, TRUE)
else if(!src.mob_negates_gravity())
step_towards(src, singularity)
/mob/living/proc/get_temperature(datum/gas_mixture/environment)
var/loc_temp = environment ? environment.temperature : T0C
if(isobj(loc))
var/obj/oloc = loc
var/obj_temp = oloc.return_temperature()
if(obj_temp != null)
loc_temp = obj_temp
else if(isspaceturf(get_turf(src)))
var/turf/heat_turf = get_turf(src)
loc_temp = heat_turf.temperature
if(ismovable(loc))
var/atom/movable/occupied_space = loc
loc_temp = ((1 - occupied_space.contents_thermal_insulation) * loc_temp) + (occupied_space.contents_thermal_insulation * bodytemperature)
return loc_temp
/// Checks if this mob can be actively tracked by cameras / AI.
/// Can optionally be passed a user, which is the mob who is tracking src.
/mob/living/proc/can_track(mob/living/user)
//basic fast checks go first. When overriding this proc, I recommend calling ..() at the end.
if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_TRACK, user) & COMPONENT_CANT_TRACK)
return FALSE
if(!isnull(user) && src == user)
return FALSE
if(invisibility || alpha <= 50)//cloaked
return FALSE
if(!isturf(loc)) //The reason why we don't just use get_turf is because they could be in a closet, disposals, or a vehicle.
return FALSE
var/turf/T = loc
if(is_centcom_level(T.z)) //dont detect mobs on centcom
return FALSE
if(is_away_level(T.z))
return FALSE
if(onSyndieBase() && !(ROLE_SYNDICATE in user?.faction))
return FALSE
// Now, are they viewable by a camera? (This is last because it's the most intensive check)
if(!GLOB.cameranet.checkCameraVis(src))
return FALSE
return TRUE
/mob/living/proc/harvest(mob/living/user) //used for extra objects etc. in butchering
return
/mob/living/can_hold_items(obj/item/I)
return ..() && HAS_TRAIT(src, TRAIT_CAN_HOLD_ITEMS) && usable_hands
/mob/living/can_perform_action(atom/target, action_bitflags)
if(!istype(target))
CRASH("Missing target arg for can_perform_action")
if(stat != CONSCIOUS)
to_chat(src, span_warning("You are not conscious enough for this action!"))
return FALSE
if(!(interaction_flags_atom & INTERACT_ATOM_IGNORE_INCAPACITATED))
var/ignore_flags = NONE
if(interaction_flags_atom & INTERACT_ATOM_IGNORE_RESTRAINED)
ignore_flags |= INCAPABLE_RESTRAINTS
if(!(interaction_flags_atom & INTERACT_ATOM_CHECK_GRAB))
ignore_flags |= INCAPABLE_GRAB
if(INCAPACITATED_IGNORING(src, ignore_flags))
to_chat(src, span_warning("You are incapacitated at the moment!"))
return FALSE
// If the MOBILITY_UI bitflag is not set it indicates the mob's hands are cutoff, blocked, or handcuffed
// Note - AI's and borgs have the MOBILITY_UI bitflag set even though they don't have hands
// Also if it is not set, the mob could be incapcitated, knocked out, unconscious, asleep, EMP'd, etc.
if(!(mobility_flags & MOBILITY_UI) && !(action_bitflags & ALLOW_RESTING))
to_chat(src, span_warning("You don't have the mobility for this!"))
return FALSE
// NEED_HANDS is already checked by MOBILITY_UI for humans so this is for silicons
if((action_bitflags & NEED_HANDS))
if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
to_chat(src, span_warning("You hands are blocked for this action!"))
return FALSE
if(!can_hold_items(isitem(target) ? target : null)) // almost redundant if it weren't for mobs
to_chat(src, span_warning("You don't have the hands for this action!"))
return FALSE
if(!(action_bitflags & ALLOW_PAI) && ispAI(src))
to_chat(src, span_warning("Your holochasis does not allow you to do this!"))
return FALSE
if(!(action_bitflags & BYPASS_ADJACENCY) && ((action_bitflags & NOT_INSIDE_TARGET) || !recursive_loc_check(src, target)) && !CanReach(target))
if(HAS_SILICON_ACCESS(src) && !ispAI(src))
if(!(action_bitflags & ALLOW_SILICON_REACH)) // silicons can ignore range checks (except pAIs)
if(!(action_bitflags & SILENT_ADJACENCY))
to_chat(src, span_warning("You are too far away!"))
return FALSE
else // just a normal carbon mob
if((action_bitflags & FORBID_TELEKINESIS_REACH))
if(!(action_bitflags & SILENT_ADJACENCY))
to_chat(src, span_warning("You are too far away!"))
return FALSE
var/datum/dna/mob_DNA = has_dna()
if(!mob_DNA || !mob_DNA.check_mutation(/datum/mutation/telekinesis) || !tkMaxRangeCheck(src, target))
if(!(action_bitflags & SILENT_ADJACENCY))
to_chat(src, span_warning("You are too far away!"))
return FALSE
if((action_bitflags & NEED_VENTCRAWL) && !HAS_TRAIT(src, TRAIT_VENTCRAWLER_NUDE) && !HAS_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS))
to_chat(src, span_warning("You wouldn't fit!"))
return FALSE
if((action_bitflags & NEED_DEXTERITY) && !ISADVANCEDTOOLUSER(src))
to_chat(src, span_warning("You don't have the dexterity to do this!"))
return FALSE
if((action_bitflags & NEED_LITERACY) && !is_literate())
to_chat(src, span_warning("You can't comprehend any of this!"))
return FALSE
if((action_bitflags & NEED_LIGHT) && !has_light_nearby() && !has_nightvision())
to_chat(src, span_warning("You need more light to do this!"))
return FALSE
if((action_bitflags & NEED_GRAVITY) && !has_gravity())
to_chat(src, span_warning("You need gravity to do this!"))
return FALSE
return TRUE
/mob/living/proc/can_use_guns(obj/item/G)//actually used for more than guns!
if(G.trigger_guard == TRIGGER_GUARD_NONE)
to_chat(src, span_warning("You are unable to fire this!"))
return FALSE
if(G.trigger_guard != TRIGGER_GUARD_ALLOW_ALL && (!ISADVANCEDTOOLUSER(src) && !HAS_TRAIT(src, TRAIT_GUN_NATURAL)))
to_chat(src, span_warning("You try to fire [G], but can't use the trigger!"))
return FALSE
return TRUE
/mob/living/proc/update_stamina()
SEND_SIGNAL(src, COMSIG_LIVING_STAMINA_UPDATE)
update_stamina_hud()
/mob/living/carbon/alien/update_stamina()
return
/mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE, throw_type_path = /datum/thrownthing)
stop_pulling()
. = ..()
// Used in polymorph code to shapeshift mobs into other creatures
/**
* Polymorphs our mob into another mob.
* If successful, our current mob is qdeleted!
*
* what_to_randomize - what are we randomizing the mob into? See the defines for valid options.
* change_flags - only used for humanoid randomization (currently), what pool of changeflags should we draw from?
*
* Returns a mob (what our mob turned into) or null (if we failed).
*/
/mob/living/proc/wabbajack(what_to_randomize, change_flags = WABBAJACK)
if(stat == DEAD || HAS_TRAIT(src, TRAIT_GODMODE) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM))
return
if(SEND_SIGNAL(src, COMSIG_LIVING_PRE_WABBAJACKED, what_to_randomize) & STOP_WABBAJACK)
return
add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_NO_TRANSFORM), MAGIC_TRAIT)
icon = null
cut_overlays()
SetInvisibility(INVISIBILITY_ABSTRACT)
var/list/item_contents = list()
if(iscyborg(src))
var/mob/living/silicon/robot/Robot = src
// Disconnect AI's in shells
if(Robot.connected_ai)
Robot.connected_ai.disconnect_shell()
QDEL_NULL(Robot.mmi)
Robot.notify_ai(AI_NOTIFICATION_NEW_BORG)
else
for(var/obj/item/item in src)
if(!dropItemToGround(item))
if(!(item.item_flags & ABSTRACT))
qdel(item)
continue
item_contents += item
var/mob/living/new_mob
var/static/list/possible_results = list(
WABBAJACK_MONKEY,
WABBAJACK_CLOWN,
WABBAJACK_ROBOT,
WABBAJACK_SLIME,
WABBAJACK_XENO,
WABBAJACK_HUMAN,
WABBAJACK_ANIMAL,
)
// If we weren't passed one, pick a default one
what_to_randomize ||= pick(possible_results)
switch(what_to_randomize)
if(WABBAJACK_MONKEY)
new_mob = new /mob/living/carbon/human/species/monkey(loc)
if(WABBAJACK_CLOWN)
var/picked_clown = pick(typesof(/mob/living/basic/clown))
new_mob = new picked_clown(loc)
if(WABBAJACK_ROBOT)
var/static/list/robot_options = list(
/mob/living/silicon/robot = 200,
/mob/living/basic/drone/polymorphed = 200,
/mob/living/basic/bot/dedbot = 25,
/mob/living/basic/bot/cleanbot = 25,
/mob/living/basic/bot/firebot = 25,
/mob/living/basic/bot/honkbot = 25,
/mob/living/basic/bot/hygienebot = 25,
/mob/living/basic/bot/medbot/mysterious = 12,
/mob/living/basic/bot/medbot = 13,
/mob/living/basic/bot/vibebot = 25,
/mob/living/basic/hivebot/strong = 50,
/mob/living/basic/hivebot/mechanic = 50,
/mob/living/basic/netguardian = 1,
/mob/living/silicon/robot/model/syndicate = 1,
/mob/living/silicon/robot/model/syndicate/medical = 1,
/mob/living/silicon/robot/model/syndicate/saboteur = 1,
)
var/picked_robot = pick(robot_options)
new_mob = new picked_robot(loc)
if(issilicon(new_mob))
var/mob/living/silicon/robot/created_robot = new_mob
new_mob.gender = gender
new_mob.SetInvisibility(INVISIBILITY_NONE)
new_mob.job = JOB_CYBORG
created_robot.lawupdate = FALSE
created_robot.connected_ai = null
created_robot.mmi.transfer_identity(src) //Does not transfer key/client.
created_robot.clear_inherent_laws(announce = FALSE)
created_robot.clear_zeroth_law(announce = FALSE)
if(WABBAJACK_SLIME)
new_mob = new /mob/living/basic/slime/random(loc)
if(WABBAJACK_XENO)
var/picked_xeno_type
if(ckey)
picked_xeno_type = pick(
/mob/living/carbon/alien/adult/hunter,
/mob/living/carbon/alien/adult/sentinel,
/mob/living/basic/alien/maid,
)
else
picked_xeno_type = pick(
/mob/living/carbon/alien/adult/hunter,
/mob/living/basic/alien/sentinel,
/mob/living/basic/alien/maid,
)
new_mob = new picked_xeno_type(loc)
if(WABBAJACK_ANIMAL)
var/picked_animal = pick(
/mob/living/basic/ant,
/mob/living/basic/axolotl,
/mob/living/basic/bat,
/mob/living/basic/bear,
/mob/living/basic/bear/butter,
/mob/living/basic/bear/snow,
/mob/living/basic/bear/russian,
/mob/living/basic/blob_minion/blobbernaut,
/mob/living/basic/blob_minion/spore,
/mob/living/basic/butterfly,
/mob/living/basic/carp,
/mob/living/basic/carp/mega,
/mob/living/basic/carp/magic,
/mob/living/basic/carp/magic/chaos,
/mob/living/basic/chick,
/mob/living/basic/chick/permanent,
/mob/living/basic/chicken,
/mob/living/basic/cow,
/mob/living/basic/cow/moonicorn,
/mob/living/basic/crab,
/mob/living/basic/crab/evil,
/mob/living/basic/crab/kreb,
/mob/living/basic/crab/evil/kreb,
/mob/living/basic/flesh_spider,
/mob/living/basic/frog, // finally we can turn people into the most iconic polymorph form.
/mob/living/basic/deer,
/mob/living/basic/eyeball,
/mob/living/basic/goat,
/mob/living/basic/gorilla,
/mob/living/basic/gorilla/lesser,
/mob/living/basic/headslug,
/mob/living/basic/killer_tomato,
/mob/living/basic/lizard,
/mob/living/basic/lizard/space,
/mob/living/basic/lightgeist,
/mob/living/basic/migo,
/mob/living/basic/migo/hatsune,
/mob/living/basic/mining/basilisk,
/mob/living/basic/mining/brimdemon,
/mob/living/basic/mining/goldgrub,
/mob/living/basic/mining/goldgrub/baby,
/mob/living/basic/mining/goliath,
/mob/living/basic/mining/goliath/ancient/immortal,
/mob/living/basic/mining/gutlunch/warrior,
/mob/living/basic/mining/mook,
/mob/living/basic/mining/mook/worker,
/mob/living/basic/mining/mook/worker/bard,
/mob/living/basic/mining/mook/worker/tribal_chief,
/mob/living/basic/mining/legion/monkey,
/mob/living/basic/mining/legion/monkey/snow,
/mob/living/basic/mining/lobstrosity,
/mob/living/basic/mining/lobstrosity/lava,
/mob/living/basic/mining/ice_demon,
/mob/living/basic/mining/ice_whelp,
/mob/living/basic/mining/watcher,
/mob/living/basic/mining/watcher/icewing,
/mob/living/basic/mining/watcher/magmawing,
/mob/living/basic/mining/wolf,
/mob/living/basic/morph,
/mob/living/basic/mothroach,
/mob/living/basic/mothroach/bar,
/mob/living/basic/mouse,
/mob/living/basic/mushroom,
/mob/living/basic/parrot,
/mob/living/basic/pet/cat,
/mob/living/basic/pet/cat/cak,
/mob/living/basic/pet/dog/breaddog,
/mob/living/basic/pet/dog/bullterrier,
/mob/living/basic/pet/dog/corgi,
/mob/living/basic/pet/dog/corgi/exoticcorgi,
/mob/living/basic/pet/dog/corgi/narsie,
/mob/living/basic/pet/dog/pug,
/mob/living/basic/pet/gondola,
/mob/living/basic/pet/fox,
/mob/living/basic/pet/penguin,
/mob/living/basic/pet/penguin/baby,
/mob/living/basic/pet/penguin/baby/permanent,
/mob/living/basic/pet/penguin/emperor,
/mob/living/basic/pet/penguin/emperor/shamebrero,
/mob/living/basic/pony,
/mob/living/basic/pony/syndicate,
/mob/living/basic/rabbit,
/mob/living/basic/rabbit/easter,
/mob/living/basic/rabbit/easter/space,
/mob/living/basic/regal_rat,
/mob/living/basic/seedling,
/mob/living/basic/seedling/meanie,
/mob/living/basic/sheep,
/mob/living/basic/snake,
/mob/living/basic/snake/banded,
/mob/living/basic/snake/banded/harmless,
/mob/living/basic/spider/giant/tangle, // curated for the most 'interesting' ones
/mob/living/basic/spider/giant/breacher,
/mob/living/basic/spider/giant/tank,
/mob/living/basic/spider/giant/ambush,
/mob/living/basic/spider/maintenance,
/mob/living/basic/statue,
/mob/living/basic/statue/frosty,
/mob/living/basic/statue/mannequin/suspicious,
/mob/living/basic/stickman,
/mob/living/basic/stickman/dog,
/mob/living/basic/stickman/ranged,
/mob/living/basic/living_limb_flesh,
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
)
new_mob = new picked_animal(loc)
if(WABBAJACK_HUMAN)
var/mob/living/carbon/human/new_human = new(loc)
// 50% chance that we'll also randomize race
if(prob(50))
var/list/chooseable_races = list()
for(var/datum/species/species_type as anything in subtypesof(/datum/species))
if(initial(species_type.changesource_flags) & change_flags)
chooseable_races += species_type
if(length(chooseable_races))
new_human.set_species(pick(chooseable_races))
// Randomize everything but the species, which was already handled above.
new_human.randomize_human_appearance(~RANDOMIZE_SPECIES)
new_human.update_body(is_creating = TRUE)
new_human.dna.update_dna_identity()
new_mob = new_human
else
stack_trace("wabbajack() was called without an invalid randomization choice. ([what_to_randomize])")
if(!new_mob)
return
to_chat(src, span_hypnophrase(span_big("Your form morphs into that of a [what_to_randomize]!")))
// And of course, make sure they get policy for being transformed
var/poly_msg = get_policy(POLICY_POLYMORPH)
if(poly_msg)
to_chat(src, poly_msg)
// Some forms can still wear some items
for(var/obj/item/item as anything in item_contents)
new_mob.equip_to_appropriate_slot(item)
// I don't actually know why we do this
new_mob.set_combat_mode(TRUE)
// on_wabbajack is where we handle setting up the name,
// transfering the mind and observerse, and other miscellaneous
// actions that should be done before we delete the original mob.
on_wabbajacked(new_mob)
// Valid polymorph types unlock the Lepton.
if((change_flags & (WABBAJACK|MIRROR_MAGIC|MIRROR_PRIDE|RACE_SWAP)) && (SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_WABBAJACK] != TRUE))
to_chat(new_mob, span_revennotice("You have the strangest feeling, for a moment. A fragile, dizzying memory wanders into your mind.. all you can make out is-"))
to_chat(new_mob, span_hypnophrase("You sleep so it may wake. You wake so it may sleep. It wakes. Do not sleep."))
SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_WABBAJACK] = TRUE
qdel(src)
return new_mob
// Called when we are hit by a bolt of polymorph and changed
// Generally the mob we are currently in is about to be deleted
/mob/living/proc/on_wabbajacked(mob/living/new_mob)
log_message("became [new_mob.name] ([new_mob.type])", LOG_ATTACK, color = "orange")
SEND_SIGNAL(src, COMSIG_LIVING_ON_WABBAJACKED, new_mob)
new_mob.name = real_name
new_mob.real_name = real_name
// Transfer mind to the new mob (also handles actions and observers and stuff)
if(mind)
mind.transfer_to(new_mob)
// Well, no mmind, guess we should try to move a key over
else if(key)
new_mob.PossessByPlayer(key)
/mob/living/proc/unfry_mob() //Callback proc to tone down spam from multiple sizzling frying oil dipping.
REMOVE_TRAIT(src, TRAIT_OIL_FRIED, "cooking_oil_react")
//Mobs on Fire
/// Global list that containes cached fire overlays for mobs
GLOBAL_LIST_EMPTY(fire_appearances)
/mob/living/proc/ignite_mob(silent)
if(fire_stacks <= 0)
return FALSE
var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks)
if(!fire_status || fire_status.on_fire)
return FALSE
return fire_status.ignite(silent)
/**
* Extinguish all fire on the mob
*
* This removes all fire stacks, fire effects, alerts, and moods
* Signals the extinguishing.
*/
/mob/living/proc/extinguish_mob()
if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //The everlasting flames will not be extinguished
return
var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks)
if(!fire_status || !fire_status.on_fire)
return
remove_status_effect(/datum/status_effect/fire_handler/fire_stacks)
/**
* Adjust the amount of fire stacks on a mob
*
* This modifies the fire stacks on a mob.
*
* Vars:
* * stacks: int The amount to modify the fire stacks
* * fire_type: type Type of fire status effect that we apply, should be subtype of /datum/status_effect/fire_handler/fire_stacks
*/
/mob/living/proc/adjust_fire_stacks(stacks, fire_type = /datum/status_effect/fire_handler/fire_stacks)
if(stacks < 0)
if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //You can't reduce fire stacks of the everlasting flames
return
stacks = max(-fire_stacks, stacks)
apply_status_effect(fire_type, stacks)
/mob/living/proc/adjust_wet_stacks(stacks, wet_type = /datum/status_effect/fire_handler/wet_stacks)
if(HAS_TRAIT(src, TRAIT_NO_EXTINGUISH)) //The everlasting flames will not be extinguished
return
if(stacks < 0)
stacks = max(fire_stacks, stacks)
apply_status_effect(wet_type, stacks)
/**
* Set the fire stacks on a mob
*
* This sets the fire stacks on a mob, stacks are clamped between -20 and 20.
* If the fire stacks are reduced to 0 then we will extinguish the mob.
*
* Vars:
* * stacks: int The amount to set fire_stacks to
* * fire_type: type Type of fire status effect that we apply, should be subtype of /datum/status_effect/fire_handler/fire_stacks
* * remove_wet_stacks: bool If we remove all wet stacks upon doing this
*/
/mob/living/proc/set_fire_stacks(stacks, fire_type = /datum/status_effect/fire_handler/fire_stacks, remove_wet_stacks = TRUE)
if(stacks < 0) //Shouldn't happen, ever
CRASH("set_fire_stacks received negative [stacks] fire stacks")
if(remove_wet_stacks)
remove_status_effect(/datum/status_effect/fire_handler/wet_stacks)
if(stacks == 0)
remove_status_effect(fire_type)
return
apply_status_effect(fire_type, stacks, TRUE)
/mob/living/proc/set_wet_stacks(stacks, wet_type = /datum/status_effect/fire_handler/wet_stacks, remove_fire_stacks = TRUE)
if(stacks < 0)
CRASH("set_wet_stacks received negative [stacks] wet stacks")
if(remove_fire_stacks)
remove_status_effect(/datum/status_effect/fire_handler/fire_stacks)
if(stacks == 0)
remove_status_effect(wet_type)
return
apply_status_effect(wet_type, stacks, TRUE)
//Share fire evenly between the two mobs
//Called in MobBump() and Crossed()
/mob/living/proc/spreadFire(mob/living/spread_to)
if(!istype(spread_to))
return
// can't spread fire to mobs that don't catch on fire
if(HAS_TRAIT(spread_to, TRAIT_NOFIRE_SPREAD) || HAS_TRAIT(src, TRAIT_NOFIRE_SPREAD))
return
var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks)
var/datum/status_effect/fire_handler/fire_stacks/their_fire_status = spread_to.has_status_effect(/datum/status_effect/fire_handler/fire_stacks)
if(fire_status && fire_status.on_fire)
if(their_fire_status && their_fire_status.on_fire)
var/firesplit = (fire_stacks + spread_to.fire_stacks) / 2
var/fire_type = (spread_to.fire_stacks > fire_stacks) ? their_fire_status.type : fire_status.type
set_fire_stacks(firesplit, fire_type)
spread_to.set_fire_stacks(firesplit, fire_type)
return
adjust_fire_stacks(-fire_stacks / 2, fire_status.type)
spread_to.adjust_fire_stacks(fire_stacks, fire_status.type)
if(spread_to.ignite_mob())
log_message("bumped into [key_name(spread_to)] and set them on fire.", LOG_ATTACK)
return
if(!their_fire_status || !their_fire_status.on_fire)
return
spread_to.adjust_fire_stacks(-spread_to.fire_stacks / 2, their_fire_status.type)
adjust_fire_stacks(spread_to.fire_stacks, their_fire_status.type)
ignite_mob()
/**
* Gets the fire overlay to use for this mob
*
* Args:
* * stacks: Current amount of fire_stacks
* * on_fire: If we're lit on fire
*
* Return a mutable appearance, the overlay that will be applied.
*/
/mob/living/proc/get_fire_overlay(stacks, on_fire)
RETURN_TYPE(/mutable_appearance)
return null
/**
* Handles effects happening when mob is on normal fire
*
* Vars:
* * seconds_per_tick
* * times_fired
* * fire_handler: Current fire status effect that called the proc
*/
/mob/living/proc/on_fire_stack(seconds_per_tick, datum/status_effect/fire_handler/fire_stacks/fire_handler)
return
//Mobs on Fire end
// used by secbot and monkeys Crossed
/mob/living/proc/knockOver(mob/living/carbon/C)
if(C.key) //save us from monkey hordes
C.visible_message(span_warning(pick( \
"[C] dives out of [src]'s way!", \
"[C] stumbles over [src]!", \
"[C] jumps out of [src]'s path!", \
"[C] trips over [src] and falls!", \
"[C] topples over [src]!", \
"[C] leaps out of [src]'s way!")))
C.Paralyze(40)
/mob/living/can_be_pulled(user, force)
return ..() && !(buckled?.buckle_prevents_pull)
/// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment.
/mob/living/proc/on_fall()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_LIVING_THUD)
return
/mob/living/forceMove(atom/destination)
if(!currently_z_moving)
stop_pulling()
if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED))
buckled.unbuckle_mob(src, force = TRUE)
if(has_buckled_mobs())
unbuckle_all_mobs(force = TRUE)
refresh_gravity()
. = ..()
if(. && client)
reset_perspective()
/mob/living/proc/update_z(new_z) // 1+ to register, null to unregister
if(registered_z == new_z)
return
if(registered_z)
SSmobs.clients_by_zlevel[registered_z] -= src
if(isnull(client))
registered_z = null
return
//Check the amount of clients exists on the Z level we're leaving from,
//this excludes us because at this point we are not registered to any z level.
var/old_level_new_clients = (registered_z ? SSmobs.clients_by_zlevel[registered_z].len : null)
//No one is left after we're gone, shut off inactive ones
if(registered_z && old_level_new_clients == 0)
for(var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[registered_z])
controller.set_ai_status(AI_STATUS_OFF)
if(new_z)
//Check the amount of clients exists on the Z level we're moving towards, excluding ourselves.
var/new_level_old_clients = SSmobs.clients_by_zlevel[new_z].len
//We'll add ourselves to the list now so get_expected_ai_status() will know we're on the z level.
SSmobs.clients_by_zlevel[new_z] += src
if(new_level_old_clients == 0) //No one was here before, wake up all the AIs.
for (var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[new_z])
//We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future.
//This isn't a problem for AIs with a client since the client will prevent this from being called anyway.
controller.set_ai_status(controller.get_expected_ai_status())
registered_z = new_z
/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
update_z(new_turf?.z)
/mob/living/mouse_drop_receive(atom/dropping, atom/user, params)
var/mob/living/U = user
if(isliving(dropping))
var/mob/living/M = dropping
if(M.can_be_held && U.pulling == M)
M.mob_try_pickup(U)//blame kevinz
return//dont open the mobs inventory if you are picking them up
return ..()
/mob/living/proc/mob_pickup(mob/living/user)
var/obj/item/mob_holder/holder = new(get_turf(src), src, held_state, head_icon, held_lh, held_rh, worn_slot_flags)
user.visible_message(span_warning("[user] scoops up [src]!"))
user.put_in_hands(holder)
/mob/living/proc/set_name()
if(identifier == 0)
identifier = rand(1, 999)
name = "[name] ([identifier])"
real_name = name
/mob/living/proc/mob_try_pickup(mob/living/user, instant=FALSE)
if(!ishuman(user))
return
if(!user.get_empty_held_indexes())
to_chat(user, span_warning("Your hands are full!"))
return FALSE
if(buckled)
to_chat(user, span_warning("[src] is buckled to something!"))
return FALSE
if(!instant)
user.visible_message(span_warning("[user] starts trying to scoop up [src]!"), \
span_danger("You start trying to scoop up [src]..."), null, null, src)
to_chat(src, span_userdanger("[user] starts trying to scoop you up!"))
if(!do_after(user, 2 SECONDS, target = src))
return FALSE
mob_pickup(user)
return TRUE
/mob/living/proc/get_static_viruses() //used when creating blood and other infective objects
if(!LAZYLEN(diseases))
return
var/list/datum/disease/result = list()
for(var/datum/disease/D in diseases)
var/static_virus = D.Copy()
result += static_virus
return result
/mob/living/reset_perspective(atom/A)
if(!..())
return
update_sight()
update_fullscreen()
update_pipe_vision()
/// Proc used to handle the fullscreen overlay updates, realistically meant for the reset_perspective() proc.
/mob/living/proc/update_fullscreen()
if(client.eye && client.eye != src)
var/atom/client_eye = client.eye
client_eye.get_remote_view_fullscreens(src)
else
clear_fullscreen("remote_view", 0)
/mob/living/vv_edit_var(var_name, var_value)
switch(var_name)
if (NAMEOF(src, maxHealth))
if (!isnum(var_value) || var_value <= 0)
return FALSE
if(NAMEOF(src, health)) //this doesn't work. gotta use procs instead.
return FALSE
if(NAMEOF(src, resting))
set_resting(var_value)
. = TRUE
if(NAMEOF(src, lying_angle))
set_lying_angle(var_value)
. = TRUE
if(NAMEOF(src, buckled))
set_buckled(var_value)
. = TRUE
if(NAMEOF(src, num_legs))
set_num_legs(var_value)
. = TRUE
if(NAMEOF(src, usable_legs))
set_usable_legs(var_value)
. = TRUE
if(NAMEOF(src, num_hands))
set_num_hands(var_value)
. = TRUE
if(NAMEOF(src, usable_hands))
set_usable_hands(var_value)
. = TRUE
if(NAMEOF(src, body_position))
set_body_position(var_value)
. = TRUE
if(NAMEOF(src, current_size))
if(var_value == 0) //prevents divisions of and by zero.
return FALSE
update_transform(var_value/current_size)
. = TRUE
if(!isnull(.))
datum_flags |= DF_VAR_EDITED
return
. = ..()
switch(var_name)
if(NAMEOF(src, maxHealth))
updatehealth()
if(NAMEOF(src, lighting_cutoff))
sync_lighting_plane_cutoff()
/mob/living/vv_get_header()
. = ..()
var/refid = REF(src)
. += {"
<br><font size='1'>[VV_HREF_TARGETREF(refid, VV_HK_GIVE_DIRECT_CONTROL, "[ckey || "no ckey"]")] / [VV_HREF_TARGETREF_1V(refid, VV_HK_BASIC_EDIT, "[real_name || "no real name"]", NAMEOF(src, real_name))]</font>
<br><font size='1'>
BRUTE:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brute' id='brute'>[getBruteLoss()]</a>
FIRE:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=fire' id='fire'>[getFireLoss()]</a>
TOXIN:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=toxin' id='toxin'>[getToxLoss()]</a>
OXY:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=oxygen' id='oxygen'>[getOxyLoss()]</a>
BRAIN:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brain' id='brain'>[get_organ_loss(ORGAN_SLOT_BRAIN)]</a>
STAMINA:<font size='1'><a href='byond://?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=stamina' id='stamina'>[getStaminaLoss()]</a>
</font>
"}
/mob/living/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---------")
VV_DROPDOWN_OPTION(VV_HK_GIVE_SPEECH_IMPEDIMENT, "Impede Speech (Slurring, stuttering, etc)")
VV_DROPDOWN_OPTION(VV_HK_ADD_MOOD, "Add Mood Event")
VV_DROPDOWN_OPTION(VV_HK_REMOVE_MOOD, "Remove Mood Event")
VV_DROPDOWN_OPTION(VV_HK_GIVE_HALLUCINATION, "Give Hallucination")
VV_DROPDOWN_OPTION(VV_HK_GIVE_DELUSION_HALLUCINATION, "Give Delusion Hallucination")
VV_DROPDOWN_OPTION(VV_HK_GIVE_GUARDIAN_SPIRIT, "Give Guardian Spirit")
VV_DROPDOWN_OPTION(VV_HK_ADMIN_RENAME, "Force Change Name")
/mob/living/vv_do_topic(list/href_list)
. = ..()
if(!.)
return
if(href_list[VV_HK_GIVE_SPEECH_IMPEDIMENT])
if(!check_rights(NONE))
return
admin_give_speech_impediment(usr)
if(href_list[VV_HK_ADD_MOOD])
if(!check_rights(NONE))
return
admin_add_mood_event(usr)
if(href_list[VV_HK_REMOVE_MOOD])
if(!check_rights(NONE))
return
admin_remove_mood_event(usr)
if(href_list[VV_HK_GIVE_HALLUCINATION])
if(!check_rights(NONE))
return
admin_give_hallucination(usr)
if(href_list[VV_HK_GIVE_DELUSION_HALLUCINATION])
if(!check_rights(NONE))
return
admin_give_delusion(usr)
if(href_list[VV_HK_GIVE_GUARDIAN_SPIRIT])
if(!check_rights(NONE))
return
admin_give_guardian(usr)
if(href_list[VV_HK_ADMIN_RENAME])
if(!check_rights(R_ADMIN))
return
var/old_name = real_name
var/new_name = sanitize_name(tgui_input_text(usr, "Enter the new name.", "Admin Rename", real_name))
if(!new_name || new_name == real_name)
return
fully_replace_character_name(real_name, new_name)
var/replace_preferences = !isnull(client) && (tgui_alert(usr, "Would you like to update the client's preference with the new name?", "Pref Overwrite", list("Yes", "No")) == "Yes")
if(replace_preferences)
client.prefs.write_preference(GLOB.preference_entries[/datum/preference/name/real_name], new_name)
log_admin("forced rename", list(
"admin" = key_name(usr),
"player" = key_name(src),
"old_name" = old_name,
"new_name" = new_name,
"updated_prefs" = replace_preferences,
))
message_admins("[key_name_admin(usr)] has forcibly changed the real name of [key_name(src)] from '[old_name]' to '[real_name]'[(replace_preferences ? " and their preferences" : "")]")
/mob/living/proc/move_to_error_room()
var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list
if(error_landmark)
forceMove(error_landmark.loc)
else
forceMove(locate(4,4,1)) //Even if the landmark is missing, this should put them in the error room.
//If you're here from seeing this error, I'm sorry. I'm so very sorry. The error landmark should be a sacred object that nobody has any business messing with, and someone did!
//Consider seeing a therapist.
var/ERROR_ERROR_LANDMARK_ERROR = "ERROR-ERROR: ERROR landmark missing!"
log_mapping(ERROR_ERROR_LANDMARK_ERROR)
CRASH(ERROR_ERROR_LANDMARK_ERROR)
/**
* Changes the inclination angle of a mob, used by humans and others to differentiate between standing up and prone positions.
*
* In BYOND-angles 0 is NORTH, 90 is EAST, 180 is SOUTH and 270 is WEST.
* This usually means that 0 is standing up, 90 and 270 are horizontal positions to right and left respectively, and 180 is upside-down.
* Mobs that do now follow these conventions due to unusual sprites should require a special handling or redefinition of this proc, due to the density and layer changes.
* The return of this proc is the previous value of the modified lying_angle if a change was successful (might include zero), or null if no change was made.
*/
/mob/living/proc/set_lying_angle(new_lying)
if(new_lying == lying_angle)
return
. = lying_angle
lying_angle = new_lying
if(lying_angle != lying_prev)
update_transform()
lying_prev = lying_angle
/**
* add_body_temperature_change Adds modifications to the body temperature
*
* This collects all body temperature changes that the mob is experiencing to the list body_temp_changes
* the aggrogate result is used to derive the new body temperature for the mob
*
* arguments:
* * key_name (str) The unique key for this change, if it already exist it will be overridden
* * amount (int) The amount of change from the base body temperature
*/
/mob/living/proc/add_body_temperature_change(key_name, amount)
body_temp_changes["[key_name]"] = amount
/**
* remove_body_temperature_change Removes the modifications to the body temperature
*
* This removes the recorded change to body temperature from the body_temp_changes list
*
* arguments:
* * key_name (str) The unique key for this change that will be removed
*/
/mob/living/proc/remove_body_temperature_change(key_name)
body_temp_changes -= key_name
/**
* get_body_temp_normal_change Returns the aggregate change to body temperature
*
* This aggregates all the changes in the body_temp_changes list and returns the result
*/
/mob/living/proc/get_body_temp_normal_change()
var/total_change = 0
if(body_temp_changes.len)
for(var/change in body_temp_changes)
total_change += body_temp_changes["[change]"]
return total_change
/**
* get_body_temp_normal Returns the mobs normal body temperature with any modifications applied
*
* This applies the result from proc/get_body_temp_normal_change() against the BODYTEMP_NORMAL and returns the result
*
* arguments:
* * apply_change (optional) Default True This applies the changes to body temperature normal
*/
/mob/living/proc/get_body_temp_normal(apply_change=TRUE)
if(!apply_change)
return BODYTEMP_NORMAL
return BODYTEMP_NORMAL + get_body_temp_normal_change()
///Returns the body temperature at which this mob will start taking heat damage.
/mob/living/proc/get_body_temp_heat_damage_limit()
return BODYTEMP_HEAT_DAMAGE_LIMIT
///Returns the body temperature at which this mob will start taking cold damage.
/mob/living/proc/get_body_temp_cold_damage_limit()
return BODYTEMP_COLD_DAMAGE_LIMIT
/atom/movable/looking_holder
invisibility = INVISIBILITY_MAXIMUM
///the direction we are operating in
var/look_direction
///actual atom on the turf, usually the owner
var/atom/movable/container
///the actual owner who is "looking"
var/mob/living/owner
/atom/movable/looking_holder/Initialize(mapload, mob/living/owner, direction)
. = ..()
look_direction = direction
src.owner = owner
update_container()
/atom/movable/looking_holder/Destroy()
owner = null
return ..()
/atom/movable/looking_holder/proc/update_container()
SIGNAL_HANDLER
var/new_container = get_atom_on_turf(owner)
if(new_container == container)
return
if(container != owner)
UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
if(container)
UnregisterSignal(container, COMSIG_MOVABLE_MOVED)
container = new_container
RegisterSignal(new_container, COMSIG_MOVABLE_MOVED, PROC_REF(mirror_move))
if(new_container != owner)
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(update_container))
/atom/movable/looking_holder/proc/mirror_move(mob/living/source, atom/oldloc, direction, Forced, old_locs)
SIGNAL_HANDLER
if(!isturf(owner.loc))
update_container()
set_glide_size(container.glide_size)
var/turf/looking_turf = owner.get_looking_turf(look_direction)
if(!looking_turf)
owner.end_look()
return
abstract_move(looking_turf)
///Checks if the user is incapacitated or on cooldown.
/mob/living/proc/can_look_up()
if(next_move > world.time)
return FALSE
if(INCAPACITATED_IGNORING(src, INCAPABLE_RESTRAINTS))
return FALSE
return TRUE
/mob/living/proc/end_look()
reset_perspective()
looking_vertically = NONE
QDEL_NULL(looking_holder)
/**
* look_up Changes the perspective of the mob to any openspace turf above the mob
*
* This also checks if an openspace turf is above the mob before looking up or resets the perspective if already looking up
*
*/
/mob/living/proc/look_up()
if(looking_vertically == UP)
return
if(looking_vertically == DOWN)
end_look()
return
if(!can_look_up())
return
changeNext_move(CLICK_CD_LOOK_UP)
var/turf/above_turf = get_looking_turf(UP)
if(!above_turf)
return
looking_vertically = UP
looking_holder = new(above_turf, src, UP)
reset_perspective(looking_holder)
/mob/living/proc/get_looking_turf(direction)
//down needs to check this floor
var/turf/check_turf = get_step_multiz(src, direction == DOWN ? NONE : direction)
if(!get_step_multiz(src, direction)) //We are at the edge z-level.
to_chat(src, span_warning("There's nothing interesting there."))
return
else if(!istransparentturf(check_turf)) //There is no turf we can look through above us
var/turf/front_hole = get_step(check_turf, dir)
if(istransparentturf(front_hole))
check_turf = front_hole
else
for(var/turf/checkhole in TURF_NEIGHBORS(check_turf))
if(istransparentturf(checkhole))
check_turf = checkhole
break
if(!istransparentturf(check_turf))
to_chat(src, span_warning("You can't see through the floor [direction == DOWN ? "below" : "above"] you."))
return
return direction == DOWN ? get_step_multiz(check_turf, DOWN) : check_turf
/**
* look_down Changes the perspective of the mob to any openspace turf below the mob
*
* This also checks if an openspace turf is below the mob before looking down or resets the perspective if already looking up
*
*/
/mob/living/proc/look_down()
if(looking_vertically == UP)
end_look()
return
if(looking_vertically == DOWN)
return
if(!can_look_up()) //if we cant look up, we cant look down.
return
changeNext_move(CLICK_CD_LOOK_UP)
var/turf/below_turf = get_looking_turf(DOWN)
if(!below_turf)
return
looking_vertically = DOWN
looking_holder = new(get_looking_turf(DOWN), src, DOWN)
reset_perspective(looking_holder)
/mob/living/set_stat(new_stat)
. = ..()
if(isnull(.))
return
if(. <= UNCONSCIOUS || new_stat >= UNCONSCIOUS)
update_body() // to update eyes
switch(.) //Previous stat.
if(CONSCIOUS)
if(stat >= UNCONSCIOUS)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT)
add_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED), STAT_TRAIT)
if(SOFT_CRIT)
if(stat >= UNCONSCIOUS)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) //adding trait sources should come before removing to avoid unnecessary updates
if(pulledby)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT)
if(UNCONSCIOUS)
if(stat != HARD_CRIT)
cure_blind(UNCONSCIOUS_TRAIT)
if(HARD_CRIT)
if(stat != UNCONSCIOUS)
cure_blind(UNCONSCIOUS_TRAIT)
if(DEAD)
remove_from_dead_mob_list()
add_to_alive_mob_list()
switch(stat) //Current stat.
if(CONSCIOUS)
if(. >= UNCONSCIOUS)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT)
remove_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_CRITICAL_CONDITION), STAT_TRAIT)
log_combat(src, src, "regained consciousness")
if(SOFT_CRIT)
if(pulledby)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) //adding trait sources should come before removing to avoid unnecessary updates
if(. >= UNCONSCIOUS)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT)
ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
log_combat(src, src, "entered soft crit")
if(UNCONSCIOUS)
if(. != HARD_CRIT)
become_blind(UNCONSCIOUS_TRAIT)
if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT))
ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
else
REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
log_combat(src, src, "lost consciousness")
if(HARD_CRIT)
if(. != UNCONSCIOUS)
become_blind(UNCONSCIOUS_TRAIT)
ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
log_combat(src, src, "entered hard crit")
if(DEAD)
REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
remove_from_alive_mob_list()
add_to_dead_mob_list()
log_combat(src, src, "died")
if(!can_hear())
stop_sound_channel(CHANNEL_AMBIENCE)
refresh_looping_ambience()
///Reports the event of the change in value of the buckled variable.
/mob/living/proc/set_buckled(new_buckled)
if(new_buckled == buckled)
return
SEND_SIGNAL(src, COMSIG_LIVING_SET_BUCKLED, new_buckled)
. = buckled
buckled = new_buckled
if(buckled)
if(!HAS_TRAIT(buckled, TRAIT_NO_IMMOBILIZE))
ADD_TRAIT(src, TRAIT_IMMOBILIZED, BUCKLED_TRAIT)
switch(buckled.buckle_lying)
if(NO_BUCKLE_LYING) // The buckle doesn't force a lying angle.
REMOVE_TRAIT(src, TRAIT_FLOORED, BUCKLED_TRAIT)
if(0) // Forcing to a standing position.
REMOVE_TRAIT(src, TRAIT_FLOORED, BUCKLED_TRAIT)
set_body_position(STANDING_UP)
set_lying_angle(0)
else // Forcing to a lying position.
ADD_TRAIT(src, TRAIT_FLOORED, BUCKLED_TRAIT)
set_body_position(LYING_DOWN)
set_lying_angle(buckled.buckle_lying)
else
remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_FLOORED), BUCKLED_TRAIT)
if(.) // We unbuckled from something.
var/atom/movable/old_buckled = .
if(old_buckled.buckle_lying == 0 && (resting || HAS_TRAIT(src, TRAIT_FLOORED))) // The buckle forced us to stay up (like a chair)
set_lying_down() // We want to rest or are otherwise floored, so let's drop on the ground.
/mob/living/set_pulledby(new_pulledby)
. = ..()
update_incapacitated()
if(. == FALSE) //null is a valid value here, we only want to return if FALSE is explicitly passed.
return
if(pulledby)
if(!. && stat == SOFT_CRIT)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT)
else if(. && stat == SOFT_CRIT)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT)
/// Updates the grab state of the mob and updates movespeed
/mob/living/setGrabState(newstate)
. = ..()
switch(grab_state)
if(GRAB_PASSIVE)
remove_movespeed_modifier(MOVESPEED_ID_MOB_GRAB_STATE)
if(GRAB_AGGRESSIVE)
add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/aggressive)
if(GRAB_NECK)
add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/neck)
if(GRAB_KILL)
add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/kill)
/// Only defined for carbons who can wear masks and helmets, we just assume other mobs have visible faces
/mob/living/proc/is_face_visible()
return TRUE
/// Sprite to show for photocopying mob butts
/mob/living/proc/get_butt_sprite()
return null
///Proc to modify the value of num_legs and hook behavior associated to this event.
/mob/living/proc/set_num_legs(new_value)
if(num_legs == new_value)
return
. = num_legs
num_legs = new_value
///Proc to modify the value of usable_legs and hook behavior associated to this event.
/mob/living/proc/set_usable_legs(new_value)
if(usable_legs == new_value)
return
if(new_value < 0) // Sanity check
stack_trace("[src] had set_usable_legs() called on them with a negative value!")
new_value = 0
. = usable_legs
usable_legs = new_value
update_usable_leg_status()
/**
* Proc that updates the status of the mob's legs without setting its leg value to something else.
*/
/mob/living/proc/update_usable_leg_status()
if(usable_legs > 0) // Gained leg usage.
REMOVE_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
else if(!(movement_type & (FLYING | FLOATING))) //Lost leg usage, not flying.
if(!usable_legs)
ADD_TRAIT(src, TRAIT_FLOORED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
if(!usable_hands)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
if(usable_legs < default_num_legs)
var/limbless_slowdown = (default_num_legs - usable_legs) * 3
if(!usable_legs && usable_hands < default_num_hands)
limbless_slowdown += (default_num_hands - usable_hands) * 3
var/list/slowdown_mods = list()
SEND_SIGNAL(src, COMSIG_LIVING_LIMBLESS_SLOWDOWN, limbless_slowdown, slowdown_mods)
for(var/num in slowdown_mods)
limbless_slowdown *= num
if(limbless_slowdown == 0)
remove_movespeed_modifier(/datum/movespeed_modifier/limbless)
return
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/limbless, multiplicative_slowdown = limbless_slowdown)
else
remove_movespeed_modifier(/datum/movespeed_modifier/limbless)
///Proc to modify the value of num_hands and hook behavior associated to this event.
/mob/living/proc/set_num_hands(new_value)
if(num_hands == new_value)
return
. = num_hands
num_hands = new_value
///Proc to modify the value of usable_hands and hook behavior associated to this event.
/mob/living/proc/set_usable_hands(new_value)
if(usable_hands == new_value)
return
. = usable_hands
usable_hands = new_value
if(new_value > .) // Gained hand usage.
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
else if(!(movement_type & (FLYING | FLOATING)) && !usable_hands && !usable_legs) //Lost a hand, not flying, no hands left, no legs.
ADD_TRAIT(src, TRAIT_IMMOBILIZED, LACKING_LOCOMOTION_APPENDAGES_TRAIT)
/// Whether or not this mob will escape from storages while being picked up/held.
/mob/living/proc/will_escape_storage()
return FALSE
//Used specifically for the clown box suicide act
/mob/living/carbon/human/will_escape_storage()
return TRUE
/// Changes the value of the [living/body_position] variable. Call this before set_lying_angle()
/mob/living/proc/set_body_position(new_value)
if(body_position == new_value)
return
if((new_value == LYING_DOWN) && !(mobility_flags & MOBILITY_LIEDOWN))
return
. = body_position
body_position = new_value
SEND_SIGNAL(src, COMSIG_LIVING_SET_BODY_POSITION, new_value, .)
if(new_value == LYING_DOWN) // From standing to lying down.
on_lying_down()
else // From lying down to standing up.
on_standing_up()
update_rest_hud_icon()
/// Proc to append behavior to the condition of being floored. Called when the condition starts.
/mob/living/proc/on_floored_start()
on_fall()
if(body_position == STANDING_UP) //force them on the ground
set_body_position(LYING_DOWN)
set_lying_angle(pick(LYING_ANGLE_EAST, LYING_ANGLE_WEST))
/// Proc to append behavior to the condition of being floored. Called when the condition ends.
/mob/living/proc/on_floored_end()
if(!resting)
get_up()
/// Proc to append behavior to the condition of being handsblocked. Called when the condition starts.
/mob/living/proc/on_handsblocked_start()
if(active_storage)
active_storage.hide_contents(src)
drop_all_held_items()
add_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_HANDS_BLOCKED)
/// Proc to append behavior to the condition of being handsblocked. Called when the condition ends.
/mob/living/proc/on_handsblocked_end()
remove_traits(list(TRAIT_UI_BLOCKED, TRAIT_PULL_BLOCKED), TRAIT_HANDS_BLOCKED)
/// Returns the attack damage type of a living mob such as [BRUTE].
/mob/living/proc/get_attack_type()
return BRUTE
/**
* Returns an assoc list of assignments and minutes for updating a client's exp time in the databse.
*
* Arguments:
* * minutes - The number of minutes to allocate to each valid role.
*/
/mob/living/proc/get_exp_list(minutes)
var/list/exp_list = list()
if(!(mind.datum_flags & DF_VAR_EDITED))
for(var/datum/antagonist/antag as anything in mind?.antag_datums)
var/flag_to_check = antag.jobban_flag || antag.pref_flag
if(flag_to_check)
exp_list[flag_to_check] = minutes
if(mind.assigned_role.title in GLOB.exp_specialmap[EXP_TYPE_SPECIAL])
exp_list[mind.assigned_role.title] = minutes
return exp_list
/**
* A proc triggered by callback when someone gets slammed by the tram and lands somewhere.
*
* This proc is used to force people to fall through things like lattice and unplated flooring at the expense of some
* extra damage, so jokers can't use half a stack of iron rods to make getting hit by the tram immediately lethal.
*/
/mob/living/proc/tram_slam_land()
if(!istype(loc, /turf/open/openspace) && !isplatingturf(loc))
return
if(isplatingturf(loc))
var/turf/open/floor/smashed_plating = loc
visible_message(span_danger("[src] is thrown violently into [smashed_plating], smashing through it and punching straight through!"),
span_userdanger("You're thrown violently into [smashed_plating], smashing through it and punching straight through!"))
apply_damage(rand(5,20), BRUTE, BODY_ZONE_CHEST)
smashed_plating.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
for(var/obj/structure/lattice/lattice in loc)
visible_message(span_danger("[src] is thrown violently into [lattice], smashing through it and punching straight through!"),
span_userdanger("You're thrown violently into [lattice], smashing through it and punching straight through!"))
apply_damage(rand(5,10), BRUTE, BODY_ZONE_CHEST)
lattice.deconstruct(FALSE)
/// Prints an ominous message if something bad is going to happen to you
/mob/living/proc/ominous_nosebleed()
to_chat(src, span_warning("You feel a bit nauseous for just a moment."))
/**
* Proc used by different station pets such as Ian and Poly so that some of their data can persist between rounds.
* This base definition only contains a trait and comsig to stop memory from being (over)written.
* Specific behavior is defined on subtypes that use it.
*/
/mob/living/proc/Write_Memory(dead, gibbed)
SHOULD_CALL_PARENT(TRUE)
if(HAS_TRAIT(src, TRAIT_DONT_WRITE_MEMORY)) //always prevent data from being written.
return FALSE
// for selective behaviors that may or may not prevent data from being written.
if(SEND_SIGNAL(src, COMSIG_LIVING_WRITE_MEMORY, dead, gibbed) & COMPONENT_DONT_WRITE_MEMORY)
return FALSE
return TRUE
/// Admin only proc for giving a certain speech impediment to this mob
/mob/living/proc/admin_give_speech_impediment(mob/admin)
if(!admin || !check_rights(NONE))
return
var/list/impediments = list()
for(var/datum/status_effect/possible as anything in typesof(/datum/status_effect/speech))
if(!initial(possible.id))
continue
impediments[initial(possible.id)] = possible
var/chosen = tgui_input_list(admin, "What speech impediment?", "Impede Speech", impediments)
if(!chosen || !ispath(impediments[chosen], /datum/status_effect/speech) || QDELETED(src) || !check_rights(NONE))
return
var/duration = tgui_input_number(admin, "How long should it last (in seconds)? Max is infinite duration.", "Duration", 0, INFINITY, 0 SECONDS)
if(!isnum(duration) || duration <= 0 || QDELETED(src) || !check_rights(NONE))
return
adjust_timed_status_effect(duration * 1 SECONDS, impediments[chosen])
/mob/living/proc/admin_add_mood_event(mob/admin)
if (!admin || !check_rights(NONE))
return
var/list/mood_events = typesof(/datum/mood_event)
var/chosen = tgui_input_list(admin, "What mood event?", "Add Mood Event", mood_events)
if (!chosen || QDELETED(src) || !check_rights(NONE))
return
mob_mood.add_mood_event("[rand(1, 50)]", chosen)
/mob/living/proc/admin_remove_mood_event(mob/admin)
if (!admin || !check_rights(NONE))
return
var/list/mood_events = list()
for (var/category in mob_mood.mood_events)
var/datum/mood_event/event = mob_mood.mood_events[category]
mood_events[event] = category
var/datum/mood_event/chosen = tgui_input_list(admin, "What mood event?", "Remove Mood Event", mood_events)
if (!chosen || QDELETED(src) || !check_rights(NONE))
return
mob_mood.clear_mood_event(mood_events[chosen])
/// Adds a mood event to the mob
/mob/living/proc/add_mood_event(category, type, ...)
if(QDELETED(mob_mood))
return
mob_mood.add_mood_event(arglist(args))
/// Clears a mood event from the mob
/mob/living/proc/clear_mood_event(category)
if(QDELETED(mob_mood))
return
mob_mood.clear_mood_event(category)
/// This should be called by games when the gamer reaches a winning state, just sends a signal
/mob/living/proc/won_game()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOB_WON_VIDEOGAME)
/// This should be called by games when the gamer reaches a losing state, just sends a signal
/mob/living/proc/lost_game()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOB_LOST_VIDEOGAME)
/// This should be called by games whenever the gamer interacts with the device, sends a signal and grants us a moodlet
/mob/living/proc/played_game()
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOB_PLAYED_VIDEOGAME)
add_mood_event("gaming", /datum/mood_event/gaming)
/**
* Helper proc for basic and simple animals to return true if the passed sentience type matches theirs
* Living doesn't have a sentience type though so it always returns false if not a basic or simple mob
*/
/mob/living/proc/compare_sentience_type(compare_type)
return FALSE
/// Proc called when TARGETED by a lazarus injector
/mob/living/proc/lazarus_revive(mob/living/reviver, malfunctioning)
revive(HEAL_ALL)
befriend(reviver)
faction = (malfunctioning) ? list("[REF(reviver)]") : list(FACTION_NEUTRAL)
var/lazarus_policy = get_policy(ROLE_LAZARUS_GOOD) || "The lazarus injector has brought you back to life! You are now friendly to everyone."
if (malfunctioning)
reviver.log_message("has revived mob [key_name(src)] with a malfunctioning lazarus injector.", LOG_GAME)
if(!isnull(src.mind))
src.mind.enslave_mind_to_creator(reviver)
to_chat(src, span_userdanger("Serve [reviver.real_name], and assist [reviver.p_them()] in completing [reviver.p_their()] goals at any cost."))
lazarus_policy = get_policy(ROLE_LAZARUS_BAD) || "You have been revived by a malfunctioning lazarus injector! You are now enslaved by whoever revived you."
to_chat(src, span_boldnotice(lazarus_policy))
/// Proc for giving a mob a new 'friend', generally used for AI control and targeting. Returns false if already friends or null if qdeleted.
/mob/living/proc/befriend(mob/living/new_friend)
SHOULD_CALL_PARENT(TRUE)
if(QDELETED(new_friend))
return
var/friend_ref = REF(new_friend)
if (faction.Find(friend_ref))
return FALSE
faction |= friend_ref
ai_controller?.insert_blackboard_key_lazylist(BB_FRIENDS_LIST, new_friend)
SEND_SIGNAL(src, COMSIG_LIVING_BEFRIENDED, new_friend)
return TRUE
/// Proc for removing a friend you added with the proc 'befriend'. Returns true if you removed a friend.
/mob/living/proc/unfriend(mob/living/old_friend)
SHOULD_CALL_PARENT(TRUE)
var/friend_ref = REF(old_friend)
if (!faction.Find(friend_ref))
return FALSE
faction -= friend_ref
ai_controller?.remove_thing_from_blackboard_key(BB_FRIENDS_LIST, old_friend)
SEND_SIGNAL(src, COMSIG_LIVING_UNFRIENDED, old_friend)
return TRUE
/**
* Common proc used to deduct money from cargo, announce the kidnapping and add src to the black market.
* Returns the black market item, for extra stuff like signals that need to be registered.
*/
/mob/living/proc/process_capture(ransom_price, black_market_price)
if(ransom_price > 0)
var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(cargo_account) //Just in case
cargo_account.adjust_money(-min(ransom_price, cargo_account.account_balance)) //Not so much, especially for competent cargo. Plus this can't be mass-triggered like it has been done with contractors
priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. As is policy we've taken a portion of the station's funds to offset the overall cost.", "Nanotrasen Asset Protection", has_important_message = TRUE)
///The price should be high enough that the contractor can't just buy 'em back with their cut alone.
var/datum/market_item/hostage/market_item = new(src, black_market_price || ransom_price)
SSmarket.markets[/datum/market/blackmarket].add_item(market_item)
if(mind)
ADD_TRAIT(mind, TRAIT_HAS_BEEN_KIDNAPPED, TRAIT_GENERIC)
return market_item
/// Admin only proc for making the mob hallucinate a certain thing
/mob/living/proc/admin_give_hallucination(mob/admin)
if(!admin || !check_rights(NONE))
return
var/chosen = select_hallucination_type(admin, "What hallucination do you want to give to [src]?", "Give Hallucination")
if(!chosen || QDELETED(src) || !check_rights(NONE))
return
if(!cause_hallucination(chosen, "admin forced by [key_name_admin(admin)]"))
to_chat(admin, "That hallucination ([chosen]) could not be run - it may be invalid with this type of mob or has no effects.")
return
message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a hallucination. (Type: [chosen])")
log_admin("[key_name(admin)] gave [src] a hallucination. (Type: [chosen])")
/// Admin only proc for giving the mob a delusion hallucination with specific arguments
/mob/living/proc/admin_give_delusion(mob/admin)
if(!admin || !check_rights(NONE))
return
var/list/delusion_args = create_delusion(admin)
if(QDELETED(src) || !check_rights(NONE) || !length(delusion_args))
return
delusion_args[2] = "admin forced"
message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a delusion hallucination. (Type: [delusion_args[1]])")
log_admin("[key_name(admin)] gave [src] a delusion hallucination. (Type: [delusion_args[1]])")
// Not using the wrapper here because we already have a list / arglist
_cause_hallucination(delusion_args)
/mob/living/proc/admin_give_guardian(mob/admin)
if(!admin || !check_rights(NONE))
return
var/del_mob = FALSE
var/mob/old_mob
var/list/possible_players = list("Poll Ghosts") + sort_list(GLOB.clients)
var/client/guardian_client = tgui_input_list(admin, "Pick the player to put in control.", "Guardian Controller", possible_players)
if(isnull(guardian_client))
return
else if(guardian_client == "Poll Ghosts")
var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as an admin created [span_notice("Guardian Spirit")] of [span_danger(real_name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, alert_pic = mutable_appearance('icons/mob/nonhuman-player/guardian.dmi', "magicexample"), jump_target = src, role_name_text = "guardian spirit", amount_to_pick = 1)
if(chosen_one)
guardian_client = chosen_one.client
else
tgui_alert(admin, "No ghost candidates.", "Guardian Controller")
return
else
old_mob = guardian_client.mob
if(isobserver(old_mob) || tgui_alert(admin, "Do you want to delete [guardian_client]'s old mob?", "Guardian Controller", list("Yes"," No")) == "Yes")
del_mob = TRUE
var/picked_type = tgui_input_list(admin, "Pick the guardian type.", "Guardian Controller", subtypesof(/mob/living/basic/guardian))
var/picked_theme = tgui_input_list(admin, "Pick the guardian theme.", "Guardian Controller", list(GUARDIAN_THEME_TECH, GUARDIAN_THEME_MAGIC, GUARDIAN_THEME_CARP, GUARDIAN_THEME_MINER, "Random"))
if(picked_theme == "Random")
picked_theme = null //holopara code handles not having a theme by giving a random one
var/picked_name = tgui_input_text(admin, "Name the guardian, leave empty to let player name it.", "Guardian Controller", max_length = MAX_NAME_LEN)
var/picked_color = input(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") as color|null
if(tgui_alert(admin, "Confirm creation.", "Guardian Controller", list("Yes", "No")) != "Yes")
return
var/mob/living/basic/guardian/summoned_guardian = new picked_type(src, picked_theme)
summoned_guardian.set_summoner(src, different_person = TRUE)
if(picked_name)
summoned_guardian.fully_replace_character_name(null, picked_name)
if(picked_color)
summoned_guardian.set_guardian_colour(picked_color)
summoned_guardian.PossessByPlayer(guardian_client?.key)
guardian_client?.init_verbs()
if(del_mob)
qdel(old_mob)
message_admins(span_adminnotice("[key_name_admin(admin)] gave a guardian spirit controlled by [guardian_client || "AI"] to [src]."))
log_admin("[key_name(admin)] gave a guardian spirit controlled by [guardian_client] to [src].")
BLACKBOX_LOG_ADMIN_VERB("Give Guardian Spirit")
/mob/living/verb/lookup()
set name = "Look Up"
set category = "IC"
if(looking_vertically)
to_chat(src, "You set your head straight again.")
end_look()
return
var/turf/current_turf = get_turf(src)
var/turf/above_turf = GET_TURF_ABOVE(current_turf)
//Check if turf above exists
if(!above_turf)
to_chat(src, span_warning("There's nothing interesting above. Better keep your eyes ahead."))
return
to_chat(src, "You tilt your head upwards.")
look_up()
/mob/living/verb/lookdown()
set name = "Look Down"
set category = "IC"
if(looking_vertically)
to_chat(src, "You set your head straight again.")
end_look()
return
var/turf/current_turf = get_turf(src)
var/turf/below_turf = GET_TURF_BELOW(current_turf)
//Check if turf below exists
if(!below_turf)
to_chat(src, span_warning("There's nothing interesting below. Better keep your eyes ahead."))
return
to_chat(src, "You tilt your head downwards.")
look_down()
/**
* Totals the physical cash on the mob and returns the total.
*/
/mob/living/proc/tally_physical_credits()
//Here is all the possible non-ID payment methods.
var/list/counted_money = list()
var/physical_cash_total = 0
for(var/obj/item/credit as anything in typecache_filter_list(get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits.
physical_cash_total += credit.get_item_credit_value()
counted_money += credit
if(is_type_in_typecache(pulling, GLOB.allowed_money)) //Coins(Pulled).
var/obj/item/counted_credit = pulling
physical_cash_total += counted_credit.get_item_credit_value()
counted_money += counted_credit
return round(physical_cash_total)
/// Returns an arbitrary number which very roughly correlates with how buff you look
/mob/living/proc/calculate_fitness()
var/athletics_level = mind?.get_skill_level(/datum/skill/athletics) || 1
var/damage = (melee_damage_lower + melee_damage_upper) / 2
return ceil(damage * (ceil(athletics_level / 2)) * maxHealth)
/// Create a report string about how strong this person looks, generated in a somewhat arbitrary fashion
/mob/living/proc/compare_fitness(mob/living/scouter)
if (HAS_TRAIT(src, TRAIT_UNKNOWN))
return span_warning("It's impossible to tell whether this person lifts.")
var/our_fitness_level = calculate_fitness()
var/their_fitness_level = scouter.calculate_fitness()
var/comparative_fitness = our_fitness_level / their_fitness_level
if (comparative_fitness > 2)
scouter.set_jitter_if_lower(comparative_fitness SECONDS)
return "[span_notice("You'd estimate [p_their()] fitness level at about...")] [span_boldwarning("What?!? [our_fitness_level]???")]"
return span_notice("You'd estimate [p_their()] fitness level at about [our_fitness_level]. [comparative_fitness <= 0.33 ? "Pathetic." : ""]")
///Performs the aftereffects of blocking a projectile.
/mob/living/proc/block_projectile_effects()
var/static/list/icon/blocking_overlay
if(isnull(blocking_overlay))
blocking_overlay = list(
mutable_appearance('icons/mob/effects/blocking.dmi', "wow"),
mutable_appearance('icons/mob/effects/blocking.dmi', "nice"),
mutable_appearance('icons/mob/effects/blocking.dmi', "good"),
)
ADD_TRAIT(src, TRAIT_BLOCKING_PROJECTILES, BLOCKING_TRAIT)
var/icon/selected_overlay = pick(blocking_overlay)
add_overlay(selected_overlay)
playsound(src, 'sound/items/weapons/fwoosh.ogg', 90, FALSE, frequency = 0.7)
update_transform(1.25)
addtimer(CALLBACK(src, PROC_REF(end_block_effects), selected_overlay), 0.6 SECONDS)
///Remoevs the effects of blocking a projectile and allows the user to block another.
/mob/living/proc/end_block_effects(selected_overlay)
REMOVE_TRAIT(src, TRAIT_BLOCKING_PROJECTILES, BLOCKING_TRAIT)
cut_overlay(selected_overlay)
update_transform(0.8)
/// Returns the string form of the def_zone we have hit.
/mob/living/proc/check_hit_limb_zone_name(hit_zone)
if(has_limbs)
return hit_zone
/mob/living/proc/painful_scream(force = FALSE)
if(HAS_TRAIT(src, TRAIT_ANALGESIA) && !force)
return
INVOKE_ASYNC(src, PROC_REF(emote), "scream")