mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 16:05:07 +00:00
* OpenDream TypeMaker Prep (#81830) ## About The Pull Request OpenDream is adding support for proc and var typechecking using `as` in https://github.com/OpenDreamProject/OpenDream/pull/1705 BYOND silently ignores most uses of `as`, but OpenDream can leverage it for static typing. E.g. the following code will error in OpenDream while doing nothing in BYOND: ``` /datum/proc/meep() as text return "meep" /datum/foobar/meep() return 5 ``` `Warning OD2701 at code.dm:29:8: /datum/foobar/meep(): Invalid return type "num", expected "text"` Pragmas allow these type emissions to be warnings, errors, or suppressed entirely. This PR modifies some existing uses of `as` in TG to prevent `ImplicitNullType` warnings (which is when a var with a null value doesn't explicitly have the `|null` type specified). This specific pragma is a bit opinionated so it could simply be disabled, but since this has no impact on BYOND behavior I don't see a reason not to fix these examples anyways. ## Why It's Good For The Game Typechecking. ## Changelog no cl no fun * OpenDream TypeMaker Prep --------- Co-authored-by: ike709 <ike709@users.noreply.github.com>
2725 lines
98 KiB
Plaintext
2725 lines
98 KiB
Plaintext
/mob/living/Initialize(mapload)
|
|
. = ..()
|
|
if(current_size != RESIZE_DEFAULT_SIZE)
|
|
update_transform(current_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)
|
|
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 .
|
|
|
|
//SKYRAT EDIT ADDITION START - Landing in liquids
|
|
if(impacted_turf.liquids && impacted_turf.liquids.liquid_state >= LIQUID_STATE_WAIST)
|
|
Knockdown(2 SECONDS)
|
|
return
|
|
//SKYRAT EDIT ADDITION END
|
|
// If you are incapped, you probably can't brace yourself
|
|
var/can_help_themselves = !incapacitated(IGNORE_RESTRAINTS)
|
|
if(levels <= 1 && can_help_themselves)
|
|
var/obj/item/organ/external/wings/gliders = get_organ_by_type(/obj/item/organ/external/wings)
|
|
if(HAS_TRAIT(src, TRAIT_FREERUNNING) || gliders?.can_soften_fall()) // the power of parkour or wings allows falling short distances unscathed
|
|
visible_message(
|
|
span_notice("[src] makes a hard landing on [impacted_turf] but remains unharmed from the fall."),
|
|
span_notice("You brace for the fall. You make a hard landing on [impacted_turf], but remain unharmed."),
|
|
)
|
|
Knockdown(levels * 4 SECONDS)
|
|
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 || (isfelinid(src) || istajaran(src))) // SKYRAT EDIT CHANGE - ORIGINAL: 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)
|
|
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)
|
|
//Even if we don't push/swap places, we "touched" them, so spread fire
|
|
spreadFire(M)
|
|
|
|
if(now_pushing)
|
|
return TRUE
|
|
|
|
var/they_can_move = TRUE
|
|
var/their_combat_mode = FALSE
|
|
|
|
if(isliving(M))
|
|
var/mob/living/L = M
|
|
their_combat_mode = L.combat_mode
|
|
they_can_move = L.mobility_flags & MOBILITY_MOVE
|
|
//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
|
|
//SKYRAT EDIT ADDITION BEGIN - GUNPOINT
|
|
if(L.gunpointed.len)
|
|
var/is_pointing = FALSE
|
|
for(var/datum/gunpoint/gp in L.gunpointed)
|
|
if(gp.source == src)
|
|
is_pointing = TRUE
|
|
break
|
|
if(!is_pointing)
|
|
if(!(world.time % 5))
|
|
to_chat(src, "<span class='warning'>[L] is being held at gunpoint, it's not wise to push him.</span>")
|
|
return TRUE
|
|
if(L.gunpointing)
|
|
if(!(world.time % 5))
|
|
to_chat(src, "<span class='warning'>[L] is holding someone at gunpoint, you cannot push past.</span>")
|
|
return TRUE
|
|
//SKYRAT EDIT ADDITION END
|
|
|
|
if(moving_diagonally)//no mob swap during diagonal moves.
|
|
return TRUE
|
|
|
|
if(!M.buckled && !M.has_buckled_mobs())
|
|
var/mob_swap = FALSE
|
|
var/too_strong = (M.move_resist > move_force) //can't swap with immovable objects unless they help us
|
|
if(!they_can_move) //we have to physically move them
|
|
if(!too_strong)
|
|
mob_swap = TRUE
|
|
else
|
|
//You can swap with the person you are dragging on grab intent, and restrained people in most cases
|
|
if(M.pulledby == src && !too_strong)
|
|
mob_swap = TRUE
|
|
else if(
|
|
!(HAS_TRAIT(M, TRAIT_NOMOBSWAP) || HAS_TRAIT(src, TRAIT_NOMOBSWAP)) &&\
|
|
((HAS_TRAIT(M, TRAIT_RESTRAINED) && !too_strong) || !their_combat_mode) &&\
|
|
(HAS_TRAIT(src, TRAIT_RESTRAINED) || !combat_mode)
|
|
)
|
|
mob_swap = TRUE
|
|
if(mob_swap)
|
|
//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/get_photo_description(obj/item/camera/camera)
|
|
var/list/mob_details = list()
|
|
var/list/holding = list()
|
|
var/len = length(held_items)
|
|
if(len)
|
|
for(var/obj/item/I in held_items)
|
|
if(!holding.len)
|
|
holding += "[p_They()] [p_are()] holding \a [I]"
|
|
else if(held_items.Find(I) == len)
|
|
holding += ", and \a [I]."
|
|
else
|
|
holding += ", \a [I]"
|
|
holding += "."
|
|
mob_details += "You can also see [src] on the photo[health < (maxHealth * 0.75) ? ", looking a bit hurt":""][holding ? ". [holding.Join("")]":"."]."
|
|
return mob_details.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
|
|
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, state, 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
|
|
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/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)))
|
|
//SKYRAT EDIT START - Tail coiling
|
|
if(ishuman(M))
|
|
if(zone_selected == BODY_ZONE_PRECISE_GROIN && M.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) && src.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL))
|
|
M.visible_message(span_warning("[src] coils their tail with [AM], wow is that okay in public?!"), "[src] has entwined their tail with yours!")
|
|
to_chat(src, "You entwine your tail with [AM]")
|
|
else
|
|
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"]!"))
|
|
// SKYRAT EDIT END
|
|
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(!iscarbon(src))
|
|
M.LAssailant = null
|
|
else
|
|
M.LAssailant = WEAKREF(usr)
|
|
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)
|
|
|
|
/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE)
|
|
if(M.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
|
|
M.setDir(get_dir(M, src))
|
|
var/target_pixel_x = M.base_pixel_x + M.body_position_pixel_x_offset
|
|
var/target_pixel_y = M.base_pixel_y + M.body_position_pixel_y_offset
|
|
switch(M.dir)
|
|
if(NORTH)
|
|
animate(M, pixel_x = target_pixel_x, pixel_y = target_pixel_y + offset, 3)
|
|
if(SOUTH)
|
|
animate(M, pixel_x = target_pixel_x, pixel_y = target_pixel_y - offset, 3)
|
|
if(EAST)
|
|
if(M.lying_angle == 270) //update the dragged dude's direction if we've turned
|
|
M.set_lying_angle(90)
|
|
animate(M, pixel_x = target_pixel_x + offset, pixel_y = target_pixel_y, 3)
|
|
if(WEST)
|
|
if(M.lying_angle == 90)
|
|
M.set_lying_angle(270)
|
|
animate(M, pixel_x = target_pixel_x - offset, pixel_y = target_pixel_y, 3)
|
|
|
|
/mob/living/proc/reset_pull_offsets(mob/living/M, override)
|
|
if(!override && M.buckled)
|
|
return
|
|
animate(M, pixel_x = M.base_pixel_x + M.body_position_pixel_x_offset , pixel_y = M.base_pixel_y + M.body_position_pixel_y_offset, 1)
|
|
|
|
//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())
|
|
return FALSE
|
|
|
|
return ..()
|
|
|
|
/mob/living/_pointed(atom/pointing_at)
|
|
if(!..())
|
|
return FALSE
|
|
log_message("points at [pointing_at]", LOG_EMOTE)
|
|
visible_message("<span class='infoplain'>[span_name("[src]")] points at [pointing_at].</span>", 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, text="You are unable to succumb to death! Unless you just press the UI button.", type=MESSAGE_TYPE_INFO)
|
|
return
|
|
else
|
|
to_chat(src, text="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()
|
|
|
|
/**
|
|
* Checks if a mob is incapacitated
|
|
*
|
|
* Normally being restrained, agressively grabbed, or in stasis counts as incapacitated
|
|
* unless there is a flag being used to check if it's ignored
|
|
*
|
|
* args:
|
|
* * flags (optional) bitflags that determine if special situations are exempt from being considered incapacitated
|
|
*
|
|
* bitflags: (see code/__DEFINES/status_effects.dm)
|
|
* * IGNORE_RESTRAINTS - mob in a restraint (handcuffs) is not considered incapacitated
|
|
* * IGNORE_STASIS - mob in stasis (stasis bed, etc.) is not considered incapacitated
|
|
* * IGNORE_GRAB - mob that is agressively grabbed is not considered incapacitated
|
|
**/
|
|
/mob/living/incapacitated(flags)
|
|
if(HAS_TRAIT(src, TRAIT_INCAPACITATED))
|
|
return TRUE
|
|
|
|
if(!(flags & IGNORE_RESTRAINTS) && HAS_TRAIT(src, TRAIT_RESTRAINED))
|
|
return TRUE
|
|
if(!(flags & IGNORE_GRAB) && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)
|
|
return TRUE
|
|
if(!(flags & IGNORE_STASIS) && HAS_TRAIT(src, TRAIT_STASIS))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/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/obj/item/card/id/id = get_idcard()
|
|
if(isnull(id))
|
|
return list()
|
|
return id.GetAccess()
|
|
|
|
/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) SKYRAT EDIT REMOVAL
|
|
to_chat(src, "<span class='notice'>You stand up.</span>")*/
|
|
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()
|
|
SEND_SIGNAL(src, COMSIG_LIVING_UPDATED_RESTING, resting) //SKYRAT EDIT ADDITION - GUNPOINT
|
|
|
|
|
|
/mob/living/proc/get_up(instant = FALSE)
|
|
set waitfor = FALSE
|
|
var/get_up_speed = GET_UP_FAST //SKYRAT EDIT CHANGE : if(!instant && !do_after(src, 1 SECONDS, 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))
|
|
var/stam = getStaminaLoss()
|
|
switch(FLOOR(stam,1))
|
|
if(STAMINA_THRESHOLD_MEDIUM_GET_UP to STAMINA_THRESHOLD_SLOW_GET_UP)
|
|
get_up_speed = GET_UP_MEDIUM
|
|
if(STAMINA_THRESHOLD_SLOW_GET_UP+1 to INFINITY)
|
|
get_up_speed = GET_UP_SLOW
|
|
if(!instant)
|
|
if(get_up_speed == GET_UP_SLOW) //Slow getups are easily noticable
|
|
visible_message(span_notice("[src] weakily attempts to stand up."), span_notice("You weakily attempt to stand up."))
|
|
if(!do_after(src, get_up_speed SECONDS, src, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM), extra_checks = CALLBACK(src, /mob/living/proc/rest_checks_callback), interaction_key = DOAFTER_SOURCE_GETTING_UP))
|
|
if(!body_position == STANDING_UP)
|
|
visible_message(span_warning("[src] fails to stand up."), span_warning("You fail to stand up."))
|
|
return
|
|
else
|
|
if(!do_after(src, get_up_speed SECONDS, src, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM), extra_checks = CALLBACK(src, /mob/living/proc/rest_checks_callback), interaction_key = DOAFTER_SOURCE_GETTING_UP))
|
|
return
|
|
if(pulledby && pulledby.grab_state)
|
|
to_chat(src, span_warning("You fail to stand up, you're restrained!")) //SKYRAT EDIT ADDITION END
|
|
return
|
|
if(resting || body_position == STANDING_UP || HAS_TRAIT(src, TRAIT_FLOORED))
|
|
return
|
|
to_chat(src, span_notice("You stand up.")) //SKYRAT EDIT ADDITION
|
|
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)
|
|
body_position_pixel_y_offset = 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)
|
|
// Make sure it doesn't go out of the southern bounds of the tile when standing.
|
|
body_position_pixel_y_offset = get_pixel_y_offset_standing(current_size)
|
|
|
|
/// Returns what the body_position_pixel_y_offset should be if the current size were `value`
|
|
/mob/living/proc/get_pixel_y_offset_standing(value)
|
|
var/icon/living_icon = icon(icon)
|
|
var/height = living_icon.Height()
|
|
return (value-1) * height * 0.5
|
|
|
|
/mob/living/proc/update_density()
|
|
if(HAS_TRAIT(src, TRAIT_UNDENSE))
|
|
set_density(FALSE)
|
|
else
|
|
set_density(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(status_flags & 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(mob_mask.Height() > world.icon_size || mob_mask.Width() > world.icon_size)
|
|
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() || (full_heal_flags & HEAL_ADMIN)) //in some cases you can't revive (e.g. no brain) //SKYRAT EDIT ADDITION - DNR TRAIT - Added: " || (full_heal_flags & HEAL_ADMIN)"
|
|
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)
|
|
|
|
// 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
|
|
|
|
/// Proc that only really gets called for humans, to handle bleeding overlays.
|
|
/mob/living/proc/update_wound_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/T = loc
|
|
|
|
if(pulling)
|
|
update_pull_movespeed()
|
|
|
|
. = ..()
|
|
|
|
if(moving_diagonally != FIRST_DIAG_STEP && isliving(pulledby))
|
|
var/mob/living/L = pulledby
|
|
L.set_pull_offsets(src, pulledby.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(body_position == LYING_DOWN && !buckled && prob(getBruteLoss()*200/maxHealth))
|
|
makeTrail(newloc, T, old_direction)
|
|
|
|
///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(90)
|
|
else if(direct & WEST)
|
|
set_lying_angle(270)
|
|
|
|
/mob/living/carbon/alien/adult/lying_angle_on_movement(direct)
|
|
return
|
|
|
|
/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
|
|
if(!has_gravity() || !isturf(start) || !blood_volume)
|
|
return
|
|
|
|
var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start
|
|
|
|
var/trail_type = getTrail()
|
|
if(!trail_type)
|
|
return
|
|
|
|
var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
|
|
if(blood_volume < max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
|
|
return
|
|
|
|
var/bleed_amount = bleedDragAmount()
|
|
blood_volume = max(blood_volume - bleed_amount, 0) //that depends on our brute damage.
|
|
var/newdir = get_dir(target_turf, start)
|
|
if(newdir != direction)
|
|
newdir = newdir | direction
|
|
if(newdir == (NORTH|SOUTH))
|
|
newdir = NORTH
|
|
else if(newdir == (EAST|WEST))
|
|
newdir = EAST
|
|
if((newdir in GLOB.cardinals) && (prob(50)))
|
|
newdir = REVERSE_DIR(get_dir(target_turf, start))
|
|
if(!blood_exists)
|
|
new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
|
|
|
|
for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
|
|
if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
|
|
TH.existing_dirs += newdir
|
|
TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
|
|
TH.transfer_mob_blood_dna(src)
|
|
|
|
/mob/living/carbon/human/makeTrail(turf/T)
|
|
if(HAS_TRAIT(src, TRAIT_NOBLOOD) || !is_bleeding() || HAS_TRAIT(src, TRAIT_NOBLOOD))
|
|
return
|
|
..()
|
|
|
|
///Returns how much blood we're losing from being dragged a tile, from [/mob/living/proc/makeTrail]
|
|
/mob/living/proc/bleedDragAmount()
|
|
var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
|
|
return max(1, brute_ratio * 2)
|
|
|
|
/mob/living/carbon/bleedDragAmount()
|
|
var/bleed_amount = 0
|
|
for(var/i in all_wounds)
|
|
var/datum/wound/iter_wound = i
|
|
bleed_amount += iter_wound.drag_bleed_amount()
|
|
return bleed_amount
|
|
|
|
/mob/living/proc/getTrail()
|
|
if(getBruteLoss() < 300)
|
|
return pick("ltrails_1", "ltrails_2")
|
|
else
|
|
return pick("trails_1", "trails_2")
|
|
|
|
/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
|
|
//If we're in an aggressive grab or higher, we're lying down, we're vulnerable to grabs, or we're staggered and we have some amount of stamina loss, we must resist
|
|
if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || get_timed_status_effect_duration(/datum/status_effect/staggered) && getStaminaLoss() > STAMINA_THRESHOLD_HARD_RESIST) //SKYRAT EDIT CHANGE - ORIGINAL : if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || get_timed_status_effect_duration(/datum/status_effect/staggered) && getStaminaLoss() >= 30)
|
|
var/altered_grab_state = pulledby.grab_state
|
|
if((body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || get_timed_status_effect_duration(/datum/status_effect/staggered)) && pulledby.grab_state < GRAB_KILL) //If prone, resisting out of a grab is equivalent to 1 grab state higher. won't make the grab state exceed the normal max, however
|
|
altered_grab_state++
|
|
if(staminaloss > STAMINA_THRESHOLD_HARD_RESIST)
|
|
altered_grab_state++
|
|
if(body_position == LYING_DOWN)
|
|
altered_grab_state++
|
|
var/mob/living/M = pulledby
|
|
if(M.staminaloss > STAMINA_THRESHOLD_HARD_RESIST)
|
|
altered_grab_state-- //SKYRAT EDIT END
|
|
var/resist_chance = BASE_GRAB_RESIST_CHANCE /// see defines/combat.dm, this should be baseline 60%
|
|
//SKYRAT EDIT ADDITION
|
|
// Akula grab resist
|
|
if(HAS_TRAIT(src, TRAIT_SLIPPERY))
|
|
resist_chance += AKULA_GRAB_RESIST_BONUS
|
|
// Oversized grab resist
|
|
if(HAS_TRAIT(src, TRAIT_OVERSIZED))
|
|
resist_chance += OVERSIZED_GRAB_RESIST_BONUS
|
|
if(HAS_TRAIT(pulledby, TRAIT_OVERSIZED))
|
|
resist_chance -= OVERSIZED_GRAB_RESIST_BONUS
|
|
//SKYRAT EDIT END
|
|
resist_chance = (resist_chance/altered_grab_state) ///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.
|
|
if(prob(resist_chance))
|
|
//SKYRAT EDIT ADDITION
|
|
// Akula break-out flavor
|
|
if(HAS_TRAIT(src, TRAIT_SLIPPERY))
|
|
visible_message(span_cyan("[src] slips free of [pulledby]'s grip!"), \
|
|
span_cyan("You slip free of [pulledby]'s grip!"), null, null, pulledby)
|
|
to_chat(pulledby, span_cyan("[src] slips free of your grip!"))
|
|
playsound(loc, 'sound/misc/slip.ogg', 50, TRUE, -1)
|
|
log_combat(pulledby, src, "broke grab")
|
|
pulledby.stop_pulling()
|
|
return FALSE
|
|
//SKYRAT EDIT END
|
|
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(rand(10,15))//failure to escape still imparts a pretty serious penalty //SKYRAT EDIT CHANGE: //adjustStaminaLoss(rand(15,20))//failure to escape still imparts a pretty serious penalty
|
|
visible_message("<span class='danger'>[src] struggles as they fail to break free of [pulledby]'s grip!</span>", \
|
|
"<span class='warning'>You struggle as you fail to break free of [pulledby]'s grip!</span>", null, null, pulledby)
|
|
to_chat(pulledby, "<span class='danger'>[src] struggles as they fail to break free of your grip!</span>")
|
|
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, pixel_y = pixel_y+4, time = 0.5 SECONDS, easing = EASE_OUT)
|
|
base_pixel_y += 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, pixel_y = pixel_y-4, time = 0.5 SECONDS, easing = EASE_OUT)
|
|
base_pixel_y -= 4
|
|
|
|
/mob/living/singularity_pull(S, 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(S, 14, 3, src, TRUE)
|
|
else if(!src.mob_negates_gravity())
|
|
step_towards(src,S)
|
|
|
|
/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
|
|
var/turf/T = get_turf(src)
|
|
if(!T)
|
|
return FALSE
|
|
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
|
|
if(!isnull(user) && src == user)
|
|
return FALSE
|
|
if(invisibility || alpha == 0)//cloaked
|
|
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/movable/target, action_bitflags)
|
|
if(!istype(target))
|
|
CRASH("Missing target arg for can_perform_action")
|
|
|
|
// 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 can't do that right now!"))
|
|
return FALSE
|
|
|
|
// NEED_HANDS is already checked by MOBILITY_UI for humans so this is for silicons
|
|
if((action_bitflags & NEED_HANDS))
|
|
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 physical ability to do this!"))
|
|
return FALSE
|
|
|
|
if(!Adjacent(target) && (target.loc != src) && !recursive_loc_check(src, target))
|
|
if(issilicon(src) && !ispAI(src))
|
|
if(!(action_bitflags & ALLOW_SILICON_REACH)) // silicons can ignore range checks (except pAIs)
|
|
to_chat(src, span_warning("You are too far away!"))
|
|
return FALSE
|
|
else // just a normal carbon mob
|
|
if((action_bitflags & FORBID_TELEKINESIS_REACH))
|
|
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/human/telekinesis) || !tkMaxRangeCheck(src, target))
|
|
to_chat(src, span_warning("You are too far away!"))
|
|
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()
|
|
return
|
|
|
|
/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)
|
|
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 || (GODMODE & status_flags) || 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)
|
|
item_contents += item
|
|
|
|
var/mob/living/new_mob
|
|
|
|
var/static/list/possible_results = list(
|
|
WABBAJACK_MONKEY,
|
|
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_ROBOT)
|
|
var/static/list/robot_options = list(
|
|
/mob/living/silicon/robot = 200,
|
|
/mob/living/basic/drone/polymorphed = 200,
|
|
/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/simple_animal/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,
|
|
)
|
|
else
|
|
picked_xeno_type = pick(
|
|
/mob/living/carbon/alien/adult/hunter,
|
|
/mob/living/simple_animal/hostile/alien/sentinel,
|
|
)
|
|
new_mob = new picked_xeno_type(loc)
|
|
|
|
if(WABBAJACK_ANIMAL)
|
|
var/picked_animal = pick(
|
|
/mob/living/basic/bat,
|
|
/mob/living/basic/bear,
|
|
/mob/living/basic/blob_minion/blobbernaut,
|
|
/mob/living/basic/butterfly,
|
|
/mob/living/basic/carp,
|
|
/mob/living/basic/carp/magic,
|
|
/mob/living/basic/carp/magic/chaos,
|
|
/mob/living/basic/chick,
|
|
/mob/living/basic/chicken,
|
|
/mob/living/basic/cow,
|
|
/mob/living/basic/crab,
|
|
/mob/living/basic/goat,
|
|
/mob/living/basic/gorilla,
|
|
/mob/living/basic/headslug,
|
|
/mob/living/basic/killer_tomato,
|
|
/mob/living/basic/lizard,
|
|
/mob/living/basic/mining/goliath,
|
|
/mob/living/basic/mining/watcher,
|
|
/mob/living/basic/morph,
|
|
/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/corgi,
|
|
/mob/living/basic/pet/dog/pug,
|
|
/mob/living/basic/pet/fox,
|
|
/mob/living/basic/spider/giant,
|
|
/mob/living/basic/spider/giant/hunter,
|
|
/mob/living/basic/statue,
|
|
/mob/living/basic/stickman,
|
|
/mob/living/basic/stickman/dog,
|
|
/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 randomice 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.key = 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 class='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!")]</span>")
|
|
C.Paralyze(40)
|
|
|
|
/mob/living/can_be_pulled()
|
|
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()
|
|
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)
|
|
. = ..()
|
|
if(. && client)
|
|
reset_perspective()
|
|
|
|
|
|
/mob/living/proc/update_z(new_z) // 1+ to register, null to unregister
|
|
if (registered_z != new_z)
|
|
if (registered_z)
|
|
SSmobs.clients_by_zlevel[registered_z] -= src
|
|
if (client)
|
|
if (new_z)
|
|
//Figure out how many clients were here before
|
|
var/oldlen = SSmobs.clients_by_zlevel[new_z].len
|
|
SSmobs.clients_by_zlevel[new_z] += src
|
|
for (var/I in length(SSidlenpcpool.idle_mobs_by_zlevel[new_z]) to 1 step -1) //Backwards loop because we're removing (guarantees optimal rather than worst-case performance), it's fine to use .len here but doesn't compile on 511
|
|
var/mob/living/simple_animal/SA = SSidlenpcpool.idle_mobs_by_zlevel[new_z][I]
|
|
if (SA)
|
|
if(oldlen == 0)
|
|
//Start AI idle if nobody else was on this z level before (mobs will switch off when this is the case)
|
|
SA.toggle_ai(AI_IDLE)
|
|
|
|
//If they are also within a close distance ask the AI if it wants to wake up
|
|
if(get_dist(get_turf(src), get_turf(SA)) < MAX_SIMPLEMOB_WAKEUP_RANGE)
|
|
SA.consider_wakeup() // Ask the mob if it wants to turn on it's AI
|
|
//They should clean up in destroy, but often don't so we get them here
|
|
else
|
|
SSidlenpcpool.idle_mobs_by_zlevel[new_z] -= SA
|
|
|
|
|
|
registered_z = new_z
|
|
else
|
|
registered_z = null
|
|
|
|
/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
|
|
..()
|
|
update_z(new_turf?.z)
|
|
|
|
/mob/living/MouseDrop_T(atom/dropping, atom/user)
|
|
var/mob/living/U = user
|
|
if(isliving(dropping))
|
|
var/mob/living/M = dropping
|
|
if(M.can_be_held && U.pulling == M)
|
|
return M.mob_try_pickup(U) //SKYRAT EDIT CHANGE //blame kevinz dont open the mobs inventory if you are picking them up
|
|
. = ..()
|
|
|
|
/mob/living/proc/mob_pickup(mob/living/user)
|
|
var/obj/item/clothing/head/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()
|
|
numba = rand(1, 1000)
|
|
name = "[name] ([numba])"
|
|
real_name = name
|
|
|
|
/mob/living/proc/mob_try_pickup(mob/living/user, instant=FALSE)
|
|
if(!ishuman(user))
|
|
return FALSE
|
|
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 //SKYRAT EDIT CHANGE
|
|
|
|
/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='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brute' id='brute'>[getBruteLoss()]</a>
|
|
FIRE:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=fire' id='fire'>[getFireLoss()]</a>
|
|
TOXIN:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=toxin' id='toxin'>[getToxLoss()]</a>
|
|
OXY:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=oxygen' id='oxygen'>[getOxyLoss()]</a>
|
|
BRAIN:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brain' id='brain'>[get_organ_loss(ORGAN_SLOT_BRAIN)]</a>
|
|
STAMINA:<font size='1'><a href='?_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")
|
|
|
|
/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)
|
|
|
|
/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
|
|
|
|
///Checks if the user is incapacitated or on cooldown.
|
|
/mob/living/proc/can_look_up()
|
|
if(next_move > world.time)
|
|
return FALSE
|
|
if(incapacitated(IGNORE_RESTRAINTS))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* 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(client.perspective != MOB_PERSPECTIVE) //We are already looking up.
|
|
stop_look_up()
|
|
if(!can_look_up())
|
|
return
|
|
changeNext_move(CLICK_CD_LOOK_UP)
|
|
RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(stop_look_up)) //We stop looking up if we move.
|
|
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(start_look_up)) //We start looking again after we move.
|
|
start_look_up()
|
|
|
|
/mob/living/proc/start_look_up()
|
|
SIGNAL_HANDLER
|
|
var/turf/ceiling = get_step_multiz(src, UP)
|
|
if(!ceiling) //We are at the highest z-level.
|
|
if (prob(0.1))
|
|
to_chat(src, span_warning("You gaze out into the infinite vastness of deep space, for a moment, you have the impulse to continue travelling, out there, out into the deep beyond, before your conciousness reasserts itself and you decide to stay within travelling distance of the station."))
|
|
return
|
|
to_chat(src, span_warning("There's nothing interesting up there."))
|
|
return
|
|
else if(!istransparentturf(ceiling)) //There is no turf we can look through above us
|
|
var/turf/front_hole = get_step(ceiling, dir)
|
|
if(istransparentturf(front_hole))
|
|
ceiling = front_hole
|
|
else
|
|
for(var/turf/checkhole in TURF_NEIGHBORS(ceiling))
|
|
if(istransparentturf(checkhole))
|
|
ceiling = checkhole
|
|
break
|
|
if(!istransparentturf(ceiling))
|
|
to_chat(src, span_warning("You can't see through the floor above you."))
|
|
return
|
|
|
|
reset_perspective(ceiling)
|
|
|
|
/mob/living/proc/stop_look_up()
|
|
SIGNAL_HANDLER
|
|
reset_perspective()
|
|
|
|
/mob/living/proc/end_look_up()
|
|
stop_look_up()
|
|
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
|
|
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
|
|
|
|
/**
|
|
* 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(client.perspective != MOB_PERSPECTIVE) //We are already looking down.
|
|
stop_look_down()
|
|
if(!can_look_up()) //if we cant look up, we cant look down.
|
|
return
|
|
changeNext_move(CLICK_CD_LOOK_UP)
|
|
RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(stop_look_down)) //We stop looking down if we move.
|
|
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(start_look_down)) //We start looking again after we move.
|
|
start_look_down()
|
|
|
|
/mob/living/proc/start_look_down()
|
|
SIGNAL_HANDLER
|
|
var/turf/floor = get_turf(src)
|
|
var/turf/lower_level = get_step_multiz(floor, DOWN)
|
|
if(!lower_level) //We are at the lowest z-level.
|
|
to_chat(src, span_warning("You can't see through the floor below you."))
|
|
return
|
|
else if(!istransparentturf(floor)) //There is no turf we can look through below us
|
|
var/turf/front_hole = get_step(floor, dir)
|
|
if(istransparentturf(front_hole))
|
|
floor = front_hole
|
|
lower_level = get_step_multiz(front_hole, DOWN)
|
|
else
|
|
// Try to find a hole near us
|
|
for(var/turf/checkhole in TURF_NEIGHBORS(floor))
|
|
if(istransparentturf(checkhole))
|
|
floor = checkhole
|
|
lower_level = get_step_multiz(checkhole, DOWN)
|
|
break
|
|
if(!istransparentturf(floor))
|
|
to_chat(src, span_warning("You can't see through the floor below you."))
|
|
return
|
|
|
|
reset_perspective(lower_level)
|
|
|
|
/mob/living/proc/stop_look_down()
|
|
SIGNAL_HANDLER
|
|
reset_perspective()
|
|
|
|
/mob/living/proc/end_look_down()
|
|
stop_look_down()
|
|
UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE)
|
|
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
|
|
|
|
|
|
/mob/living/set_stat(new_stat)
|
|
. = ..()
|
|
if(isnull(.))
|
|
return
|
|
|
|
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)
|
|
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)
|
|
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)
|
|
if(HARD_CRIT)
|
|
if(. != UNCONSCIOUS)
|
|
become_blind(UNCONSCIOUS_TRAIT)
|
|
ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
|
|
if(DEAD)
|
|
REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT)
|
|
remove_from_alive_mob_list()
|
|
add_to_dead_mob_list()
|
|
|
|
|
|
///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)
|
|
. = ..()
|
|
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
|
|
|
|
|
|
///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
|
|
|
|
if(new_value > .) // 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
|
|
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
|
|
|
|
/// Sets the mob's hunger levels to a safe overall level. Useful for TRAIT_NOHUNGER species changes.
|
|
/mob/living/proc/set_safe_hunger_level()
|
|
// Nutrition reset and alert clearing.
|
|
nutrition = NUTRITION_LEVEL_FED
|
|
clear_alert(ALERT_NUTRITION)
|
|
satiety = 0
|
|
|
|
// Trait removal if obese
|
|
if(HAS_TRAIT_FROM(src, TRAIT_FAT, OBESITY))
|
|
if(overeatduration >= (200 SECONDS))
|
|
to_chat(src, span_notice("Your transformation restores your body's natural fitness!"))
|
|
|
|
REMOVE_TRAIT(src, TRAIT_FAT, OBESITY)
|
|
remove_movespeed_modifier(/datum/movespeed_modifier/obesity)
|
|
update_worn_undersuit()
|
|
update_worn_oversuit()
|
|
|
|
// Reset overeat duration.
|
|
overeatduration = 0
|
|
|
|
|
|
/// 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.
|
|
//SKYRAT EDIT ADDITION BEGIN - SOUNDS
|
|
if(has_gravity())
|
|
playsound(src, "bodyfall", 50, TRUE)
|
|
//SKYRAT EDIT END
|
|
on_lying_down()
|
|
else // From lying down to standing up.
|
|
on_standing_up()
|
|
|
|
|
|
/// Proc to append behavior to the condition of being floored. Called when the condition starts.
|
|
/mob/living/proc/on_floored_start()
|
|
if(body_position == STANDING_UP) //force them on the ground
|
|
set_body_position(LYING_DOWN)
|
|
set_lying_angle(pick(90, 270))
|
|
on_fall()
|
|
|
|
|
|
/// 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()
|
|
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 && mind.special_role && !(mind.datum_flags & DF_VAR_EDITED))
|
|
exp_list[mind.special_role] = 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)
|
|
|
|
/**
|
|
* 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)
|
|
|
|
/mob/living/played_game()
|
|
. = ..()
|
|
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.
|
|
/mob/living/proc/befriend(mob/living/new_friend)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
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
|
|
|
|
/// 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/list/candidates = SSpolling.poll_ghost_candidates("Do you want to play as an admin created Guardian Spirit of [real_name]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, ignore_category = POLL_IGNORE_HOLOPARASITE, pic_source = src, role_name_text = "guardian spirit")
|
|
if(LAZYLEN(candidates))
|
|
var/mob/dead/observer/candidate = pick(candidates)
|
|
guardian_client = candidate.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")
|
|
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.key = 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(client.perspective != MOB_PERSPECTIVE)
|
|
end_look_up()
|
|
else
|
|
look_up()
|
|
|
|
/mob/living/verb/lookdown()
|
|
set name = "Look Down"
|
|
set category = "IC"
|
|
|
|
if(client.perspective != MOB_PERSPECTIVE)
|
|
end_look_down()
|
|
else
|
|
look_down()
|