mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-20 14:56:34 +01:00
1474 lines
41 KiB
Plaintext
1474 lines
41 KiB
Plaintext
/**
|
|
* Base definition of mob.
|
|
*/
|
|
/mob
|
|
//* Actionspeed *//
|
|
/// List of action speed modifiers applying to this mob
|
|
/// * Lazy list, see mob_movespeed.dm
|
|
var/list/actionspeed_modifiers
|
|
/// List of action speed modifiers ignored by this mob. List -> List (id) -> List (sources)
|
|
/// * Lazy list, see mob_movespeed.dm
|
|
var/list/actionspeed_modifier_immunities
|
|
|
|
//* Buckling *//
|
|
/// Atom we're buckled to
|
|
var/atom/movable/buckled
|
|
/// Atom we're buckl**ing** to. Used to stop stuff like lava from incinerating those who are mid buckle.
|
|
// todo: can this be put in an existing bitfield somewhere else?
|
|
var/atom/movable/buckling
|
|
|
|
//* Emotes *//
|
|
/// running emotes, associated to context the emote can set
|
|
var/list/datum/emote/emotes_running
|
|
/// our default emote classes
|
|
var/emote_class = EMOTE_CLASS_IS_BODY
|
|
|
|
//* HUD (Atom) *//
|
|
/// HUDs to initialize, typepaths
|
|
var/list/atom_huds_to_initialize
|
|
|
|
//* Impairments *//
|
|
/// active feign_impairment types
|
|
/// * lazy list
|
|
var/list/impairments_feigned
|
|
|
|
//* Mind *//
|
|
/// Our owning mind.
|
|
/// * Minds are what a 'character' is, or perhaps the soul or whatever you want to call it.
|
|
/// A player / character is recoverable in-round as long as something can still reference the mind.
|
|
/// * Generally, if you want to reference a body, reference this mob; if you want to reference
|
|
/// a character, reference the mind if you plan to keep it around for cloning or ghost creation
|
|
/// or something, and reference the mind's `mind_ref` via `get_mind_ref()` if you're just
|
|
/// using it for checks.
|
|
var/datum/mind/mind
|
|
/// Last owning mind.
|
|
/// * Set by inbound mind transfers but not cleared by outbound.
|
|
/// Code can use this to get a reference (if the mind still exists)
|
|
/// to the last inhabiting player / character / whatever of this mob.
|
|
var/datum/mind_ref/mind_last
|
|
/// 'Locking' mind ref.
|
|
/// * Generally only set on players; Mind transfers will not clear this.
|
|
/// Resleeving reads this to prevent body impersonation.
|
|
/// * Should be treated as volatile. Non-IC (or even special antag things) can clear this.
|
|
var/datum/mind_ref/mind_resleeving_lock
|
|
|
|
//* Inventory *//
|
|
/// our inventory datum, if any.
|
|
/// * Set to typepath to initialize.
|
|
var/datum/inventory/inventory
|
|
|
|
//* Movespeed *//
|
|
/// List of movement speed modifiers applying to this mob
|
|
/// * This is a lazy list.
|
|
var/list/movespeed_modifiers
|
|
/// List of movement speed modifiers ignored by this mob. List -> List (id) -> List (sources)
|
|
/// * This is a lazy list.
|
|
var/list/movespeed_modifier_immunities
|
|
/// The calculated mob speed slowdown based on the modifiers list
|
|
var/movespeed_hyperbolic
|
|
|
|
//* Status Indicators *//
|
|
/// datum path = list of sources
|
|
var/list/status_indicators
|
|
var/list/status_indicator_overlays
|
|
|
|
/**
|
|
* Intialize a mob
|
|
*
|
|
* Adds to global lists
|
|
* * GLOB.mob_list
|
|
* * dead_mob_list - if mob is dead
|
|
* * living_mob_list - if the mob is alive
|
|
*
|
|
* Other stuff:
|
|
* * Sets the mob focus to itself
|
|
* * Generates huds
|
|
* * Creates inventory
|
|
* * If there are any global alternate apperances apply them to this mob
|
|
* * Intialize the transform of the mob
|
|
*/
|
|
/mob/Initialize(mapload)
|
|
// mob lists
|
|
mob_list_register()
|
|
// actions
|
|
actions_controlled = new /datum/action_holder/mob_actor(src)
|
|
actions_innate = new /datum/action_holder/mob_actor(src)
|
|
// physiology
|
|
init_physiology()
|
|
// atom HUDs
|
|
prepare_huds()
|
|
// key focus
|
|
set_key_focus(src)
|
|
// signal
|
|
SEND_GLOBAL_SIGNAL(COMSIG_GLOBAL_MOB_NEW, src)
|
|
// abilities
|
|
init_abilities()
|
|
// inventory
|
|
init_inventory()
|
|
// rendering
|
|
init_rendering()
|
|
// resize
|
|
update_transform()
|
|
// offset
|
|
reset_pixel_offsets()
|
|
// update gravity
|
|
update_gravity()
|
|
// movespeed
|
|
update_movespeed_base()
|
|
// actionspeed
|
|
initialize_actionspeed()
|
|
// ssd overlay
|
|
update_ssd_overlay()
|
|
// iff factions
|
|
init_iff()
|
|
return ..()
|
|
|
|
/mob/Destroy()
|
|
// this kicks out client
|
|
// this is needed to be early because we destroy stuff like inventory
|
|
// TODO: can we have this ran even earlier?
|
|
// ideally we want to preserve as much state as possible so
|
|
// we don't want to run further-down behavior before this
|
|
ghostize()
|
|
// key focus
|
|
set_key_focus(null)
|
|
// status effects
|
|
for(var/id in status_effects)
|
|
var/datum/status_effect/effect = status_effects[id]
|
|
qdel(effect)
|
|
status_effects = null
|
|
// mob lists
|
|
mob_list_unregister()
|
|
// signal
|
|
SEND_GLOBAL_SIGNAL(COMSIG_GLOBAL_MOB_DEL, src)
|
|
// abilities
|
|
dispose_abilities()
|
|
// inventory
|
|
QDEL_NULL(inventory)
|
|
// actions
|
|
QDEL_NULL(actions_controlled)
|
|
QDEL_NULL(actions_innate)
|
|
// run legacy actions
|
|
do
|
|
// todo: remove machine
|
|
unset_machine()
|
|
// hud
|
|
for(var/alert in alerts)
|
|
clear_alert(alert)
|
|
// mind
|
|
if(!isnull(mind))
|
|
if(mind.current == src)
|
|
// mind is ours, let it disassociate
|
|
// todo: legacy spell
|
|
spellremove(src)
|
|
mind?.disassociate()
|
|
else
|
|
// mind is not ours, null it out
|
|
mind = null
|
|
// abilities
|
|
QDEL_NULL(ability_master)
|
|
// spells
|
|
QDEL_LIST(spell_list)
|
|
QDEL_LIST(spell_masters)
|
|
// cleanup screen objects
|
|
QDEL_NULL(hands)
|
|
QDEL_NULL(pullin)
|
|
QDEL_NULL(purged)
|
|
QDEL_NULL(internals)
|
|
QDEL_NULL(oxygen)
|
|
QDEL_NULL(i_select)
|
|
QDEL_NULL(m_select)
|
|
QDEL_NULL(toxin)
|
|
QDEL_NULL(fire)
|
|
QDEL_NULL(bodytemp)
|
|
QDEL_NULL(healths)
|
|
QDEL_NULL(throw_icon)
|
|
QDEL_NULL(nutrition_icon)
|
|
QDEL_NULL(pressure)
|
|
QDEL_NULL(pain)
|
|
QDEL_NULL(item_use_icon)
|
|
QDEL_NULL(gun_move_icon)
|
|
QDEL_NULL(gun_setting_icon)
|
|
QDEL_NULL(spell_masters)
|
|
QDEL_NULL(zone_sel)
|
|
while(FALSE)
|
|
// get rid of our shit and nullspace everything first..
|
|
..()
|
|
// rendering
|
|
QDEL_NULL(hud_used)
|
|
dispose_rendering()
|
|
// perspective; it might be gone now because self perspective is destroyed in ..()
|
|
using_perspective?.remove_mob(src, TRUE)
|
|
// physiology
|
|
QDEL_NULL(physiology)
|
|
physiology_modifiers = null
|
|
// movespeed
|
|
movespeed_modifiers = null
|
|
// actionspeed
|
|
actionspeed_modifiers = null
|
|
return QDEL_HINT_QUEUE
|
|
|
|
/**
|
|
* Checks if we're relatively important for logging
|
|
* * As a rule of thumb, if a player is attacking or being attacked, something should be logged.
|
|
* This checks if we're important enough to invoke logs whether on the sending or receiving sides.
|
|
*
|
|
* @return truth-y or false-y value, not necessarily TRUE / FALSE
|
|
*/
|
|
/mob/proc/is_potentially_important_for_logs()
|
|
return ckey || mind
|
|
|
|
//* Mob List Registration *//
|
|
|
|
/mob/proc/mob_list_register()
|
|
GLOB.mob_list += src
|
|
if(stat == DEAD)
|
|
dead_mob_list += src
|
|
else
|
|
living_mob_list += src
|
|
|
|
/mob/proc/mob_list_unregister()
|
|
GLOB.mob_list -= src
|
|
dead_mob_list -= src
|
|
living_mob_list -= src
|
|
|
|
/mob/proc/mob_list_update_stat(old_stat, new_stat)
|
|
if(old_stat == new_stat)
|
|
return
|
|
switch(old_stat)
|
|
if(DEAD)
|
|
dead_mob_list -= src
|
|
else
|
|
living_mob_list -= src
|
|
switch(new_stat)
|
|
if(DEAD)
|
|
dead_mob_list += src
|
|
else
|
|
living_mob_list += src
|
|
|
|
/**
|
|
* Generate the tag for this mob
|
|
*
|
|
* This is simply "mob_"+ a global incrementing counter that goes up for every mob
|
|
*/
|
|
/mob/generate_tag()
|
|
tag = "mob_[++next_mob_id]"
|
|
|
|
/mob/statpanel_data(client/C)
|
|
. = ..()
|
|
if(C.statpanel_tab("Status"))
|
|
INJECT_STATPANEL_DATA_ENTRY(., "Ping", "[round(client.lastping,1)]ms (Avg: [round(client.avgping,1)]ms)")
|
|
INJECT_STATPANEL_DATA_ENTRY(., "Map", "[(LEGACY_MAP_DATUM)?.name || "Loading..."]")
|
|
if(!isnull(SSmapping.next_station) && !isnull(SSmapping.loaded_station) && (SSmapping.next_station.name != SSmapping.loaded_station.name))
|
|
INJECT_STATPANEL_DATA_ENTRY(., "Next Map", "[SSmapping.next_station.name]")
|
|
|
|
/// Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
|
// todo: refactor
|
|
/mob/show_message(msg, type, alt, alt_type)
|
|
if(!client && !teleop)
|
|
return
|
|
|
|
if(!saycode_type_eligible(type))
|
|
if(alt && saycode_type_eligible(alt_type))
|
|
msg = alt
|
|
type = alt_type
|
|
else
|
|
return
|
|
|
|
if(IS_ALIVE_BUT_UNCONSCIOUS(src))
|
|
to_chat(src,"<I>... You can almost hear someone talking ...</I>", type = MESSAGE_TYPE_LOCALCHAT)
|
|
else
|
|
to_chat(src,msg, type = MESSAGE_TYPE_LOCALCHAT)
|
|
if(teleop)
|
|
to_chat(teleop, create_text_tag("body", "BODY:", teleop) + "[msg]", type = MESSAGE_TYPE_LOCALCHAT)
|
|
|
|
/mob/proc/saycode_type_eligible(type)
|
|
switch(type)
|
|
if(SAYCODE_TYPE_VISIBLE)
|
|
return !is_blind()
|
|
if(SAYCODE_TYPE_AUDIBLE)
|
|
return !is_deaf()
|
|
if(SAYCODE_TYPE_CONSCIOUS)
|
|
return IS_CONSCIOUS(src)
|
|
if(SAYCODE_TYPE_LIVING)
|
|
return !IS_DEAD(src)
|
|
if(SAYCODE_TYPE_ALWAYS)
|
|
return TRUE
|
|
return TRUE
|
|
|
|
/**
|
|
* Show a message to all mobs in earshot of this one
|
|
*
|
|
* This would be for audible actions by the src mob
|
|
*
|
|
* vars:
|
|
* * message is the message output to anyone who can hear.
|
|
* * self_message (optional) is what the src mob hears.
|
|
* * deaf_message (optional) is what deaf people will see.
|
|
* * hearing_distance (optional) is the range, how many tiles away the message can be heard.
|
|
*/
|
|
/mob/audible_message(var/message, var/deaf_message, var/hearing_distance, var/self_message)
|
|
|
|
var/range = hearing_distance || world.view
|
|
var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE)
|
|
|
|
var/list/hearing_mobs = hear["mobs"]
|
|
var/list/hearing_objs = hear["objs"]
|
|
|
|
for(var/obj in hearing_objs)
|
|
var/obj/O = obj
|
|
O.show_message(message, 2, deaf_message, 1)
|
|
|
|
for(var/mob in hearing_mobs)
|
|
var/mob/M = mob
|
|
var/msg = message
|
|
if(self_message && M==src)
|
|
msg = self_message
|
|
M.show_message(msg, 2, deaf_message, 1)
|
|
|
|
/mob/proc/findname(msg)
|
|
for(var/mob/M in GLOB.mob_list)
|
|
if (M.real_name == "[msg]")
|
|
return M
|
|
return 0
|
|
|
|
#define UNBUCKLED 0
|
|
#define PARTIALLY_BUCKLED 1
|
|
#define FULLY_BUCKLED 2
|
|
|
|
/mob/proc/is_buckled()
|
|
// Preliminary work for a future buckle rewrite,
|
|
// where one might be fully restrained (like an elecrical chair), or merely secured (shuttle chair, keeping you safe but not otherwise restrained from acting)
|
|
if(!buckled)
|
|
return UNBUCKLED
|
|
return restrained() ? FULLY_BUCKLED : PARTIALLY_BUCKLED
|
|
|
|
/mob/proc/is_blind()
|
|
return (has_status_effect(/datum/status_effect/sight/blindness) || incapacitated(INCAPACITATION_KNOCKOUT))
|
|
|
|
/mob/proc/is_deaf()
|
|
return ((sdisabilities & SDISABILITY_DEAF) || ear_deaf || incapacitated(INCAPACITATION_KNOCKOUT))
|
|
|
|
/mob/proc/is_physically_disabled()
|
|
return incapacitated(INCAPACITATION_DISABLED)
|
|
|
|
/mob/proc/incapacitated(var/incapacitation_flags = INCAPACITATION_DEFAULT)
|
|
if ((incapacitation_flags & INCAPACITATION_STUNNED) && !CHECK_MOBILITY(src, MOBILITY_CAN_USE))
|
|
return 1
|
|
|
|
if ((incapacitation_flags & INCAPACITATION_FORCELYING) && !CHECK_MOBILITY(src, MOBILITY_IS_STANDING))
|
|
return 1
|
|
|
|
if ((incapacitation_flags & INCAPACITATION_KNOCKOUT) && !CHECK_MOBILITY(src, MOBILITY_IS_CONSCIOUS))
|
|
return 1
|
|
|
|
if((incapacitation_flags & INCAPACITATION_RESTRAINED) && restrained())
|
|
return 1
|
|
|
|
if((incapacitation_flags & (INCAPACITATION_BUCKLED_PARTIALLY|INCAPACITATION_BUCKLED_FULLY)))
|
|
var/buckling = buckled()
|
|
if(buckling >= PARTIALLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_PARTIALLY))
|
|
return 1
|
|
if(buckling == FULLY_BUCKLED && (incapacitation_flags & INCAPACITATION_BUCKLED_FULLY))
|
|
return 1
|
|
|
|
return 0
|
|
|
|
#undef UNBUCKLED
|
|
#undef PARTIALLY_BUCKLED
|
|
#undef FULLY_BUCKLED
|
|
|
|
///Is the mob restrained
|
|
/mob/proc/restrained()
|
|
return
|
|
|
|
/**
|
|
* Examine a mob
|
|
*
|
|
* mob verbs are faster than object verbs. See
|
|
* [this byond forum post](https://secure.byond.com/forum/?post=1326139&page=2#comment8198716)
|
|
* for why this isn't atom/verb/examine()
|
|
*/
|
|
/mob/verb/examinate(atom/A as mob|obj|turf in view()) //It used to be oview(12), but I can't really say why
|
|
set name = "Examine"
|
|
set category = VERB_CATEGORY_IC
|
|
|
|
if(isturf(A) && !(sight & SEE_TURFS) && !(A in view(client ? client.view : world.view, src)))
|
|
// shift-click catcher may issue examinate() calls for out-of-sight turfs
|
|
return
|
|
|
|
if(is_blind()) //blind people see things differently (through touch)
|
|
to_chat(src, SPAN_WARNING("Something is there but you can't see it!"))
|
|
return
|
|
|
|
face_atom(A)
|
|
if(!isobserver(src) && !isturf(A) && (get_top_level_atom(A) != src) && get_turf(A))
|
|
for(var/mob/M in viewers(4, src))
|
|
if(M == src || M.is_blind())
|
|
continue
|
|
// if(M.client && M.client.get_preference_toggle(/datum/client_preference/examine_look))
|
|
to_chat(M, SPAN_TINYNOTICE("<b>\The [src]</b> looks at \the [A]."))
|
|
|
|
do_examinate(A)
|
|
|
|
/**
|
|
* examines something & sends results
|
|
* no pre-checks for dist/view/whatnot
|
|
*/
|
|
/mob/proc/do_examinate(atom/A)
|
|
var/list/result
|
|
if(client)
|
|
result = A.examine(src, game_range_to(src, A)) // if a tree is examined but no client is there to see it, did the tree ever really exist?
|
|
|
|
SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, A, result, game_range_to(src, A))
|
|
to_chat(src, "<blockquote class='info'>[result.Join("\n")]</blockquote>")
|
|
|
|
#define POINT_TIME (2.5 SECONDS)
|
|
|
|
/**
|
|
* Point at an atom
|
|
*
|
|
* mob verbs are faster than object verbs. See
|
|
* [this byond forum post](https://secure.byond.com/forum/?post=1326139&page=2#comment8198716)
|
|
* for why this isn't atom/verb/pointed()
|
|
*
|
|
* note: ghosts can point, this is intended
|
|
*
|
|
* visible_message will handle invisibility properly
|
|
*
|
|
* overridden here and in /mob/observer/dead for different point span classes and sanity checks
|
|
*/
|
|
/mob/verb/pointed(atom/A as mob|obj|turf in view())
|
|
set name = "Point To"
|
|
set category = VERB_CATEGORY_OBJECT
|
|
|
|
if(istype(A, /obj/effect/temp_visual/point))
|
|
return FALSE
|
|
|
|
// i am queued sometimes
|
|
_pointed(A)
|
|
|
|
/mob/proc/_pointed(atom/pointing_at)
|
|
if(client) //Clientless mobs can just go ahead and point
|
|
if(!(pointing_at in view(client.view, src)))
|
|
return FALSE
|
|
// since we dont have the datum emote for pointing, too lazy to implement! (it sets the cooldown)
|
|
// if(iscarbon(src)) // special interactions for carbons
|
|
// var/mob/living/carbon/our_carbon = src
|
|
// if(our_carbon.get_usable_hand_count() <= 0 || our_carbon.handcuffed /* || HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)*/)
|
|
// if(!TIMER_COOLDOWN_CHECK(src, "point_verb_emote_cooldown"))
|
|
// //cooldown handled in the emote.
|
|
// our_carbon.emote("point [pointing_at]")
|
|
// else
|
|
// to_chat(src, SPAN_WARNING("You need to wait before pointing again!"))
|
|
// return FALSE
|
|
|
|
point_at(pointing_at, TRUE)
|
|
return 1
|
|
|
|
/**
|
|
* Point at an atom
|
|
*
|
|
* Intended to enable and standardise the pointing animation for all atoms
|
|
*
|
|
* Not intended as a replacement for the mob verb
|
|
*/
|
|
/atom/movable/proc/point_at(atom/pointed_atom, intentional = FALSE)
|
|
if(!isturf(loc))
|
|
return FALSE
|
|
|
|
if (pointed_atom in src)
|
|
create_point_bubble(pointed_atom)
|
|
return FALSE
|
|
|
|
var/turf/tile = get_turf(pointed_atom)
|
|
if (!tile)
|
|
return FALSE
|
|
|
|
var/turf/our_tile = get_turf(src)
|
|
var/obj/visual = new /obj/effect/temp_visual/point(our_tile, invisibility)
|
|
|
|
// SEND_SIGNAL(src, COMSIG_MOVABLE_POINTED, pointed_atom, visual, intentional)
|
|
|
|
animate(visual, pixel_x = (tile.x - our_tile.x) * world.icon_size + pointed_atom.pixel_x, pixel_y = (tile.y - our_tile.y) * world.icon_size + pointed_atom.pixel_y, time = 1.7, easing = EASE_OUT)
|
|
return TRUE
|
|
|
|
/mob/point_at(atom/pointed_atom, intentional = FALSE)
|
|
. = ..()
|
|
if(.)
|
|
face_atom(pointed_atom)
|
|
|
|
/atom/movable/proc/create_point_bubble(atom/pointed_atom)
|
|
var/mutable_appearance/thought_bubble = mutable_appearance(
|
|
'icons/effects/effects.dmi',
|
|
"thought_bubble",
|
|
plane = POINT_PLANE,
|
|
appearance_flags = KEEP_APART,
|
|
)
|
|
|
|
var/mutable_appearance/pointed_atom_appearance = new(pointed_atom.appearance)
|
|
pointed_atom_appearance.blend_mode = BLEND_INSET_OVERLAY
|
|
pointed_atom_appearance.plane = FLOAT_PLANE
|
|
pointed_atom_appearance.layer = FLOAT_LAYER
|
|
pointed_atom_appearance.pixel_x = 0
|
|
pointed_atom_appearance.pixel_y = 0
|
|
thought_bubble.overlays += pointed_atom_appearance
|
|
// pointed_atom_appearance.remove_filter(HOVER_OUTLINE_FILTER)
|
|
|
|
thought_bubble.pixel_w = 16
|
|
thought_bubble.pixel_z = 32
|
|
thought_bubble.alpha = 200
|
|
|
|
var/mutable_appearance/point_visual = mutable_appearance(
|
|
'icons/mob/screen1.dmi',
|
|
"arrow"
|
|
)
|
|
|
|
thought_bubble.overlays += point_visual
|
|
|
|
add_overlay(thought_bubble)
|
|
// LAZYADD(update_overlays_on_z, thought_bubble)
|
|
addtimer(CALLBACK(src, PROC_REF(clear_point_bubble), thought_bubble), POINT_TIME)
|
|
|
|
/atom/movable/proc/clear_point_bubble(mutable_appearance/thought_bubble)
|
|
// LAZYREMOVE(update_overlays_on_z, thought_bubble)
|
|
cut_overlay(thought_bubble)
|
|
|
|
#undef POINT_TIME
|
|
|
|
/**
|
|
* Get the notes of this mob
|
|
*
|
|
* This actually gets the mind datums notes
|
|
*/
|
|
/mob/verb/memory()
|
|
set name = "Notes"
|
|
set category = VERB_CATEGORY_IC
|
|
if(mind)
|
|
mind.show_memory(src)
|
|
else
|
|
to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
|
|
|
|
/**
|
|
* Add a note to the mind datum
|
|
*/
|
|
/mob/verb/add_memory(msg as message)
|
|
set name = "Add Note"
|
|
set category = VERB_CATEGORY_IC
|
|
|
|
msg = sanitize(msg)
|
|
|
|
if(mind)
|
|
mind.store_memory(msg)
|
|
else
|
|
to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
|
|
|
|
/mob/proc/store_memory(msg as message, popup, sane = 1)
|
|
msg = copytext(msg, 1, MAX_MESSAGE_LEN)
|
|
|
|
if (sane)
|
|
msg = sanitize(msg)
|
|
|
|
if((length(memory) + length(msg)) > MAX_MESSAGE_LEN)
|
|
return
|
|
|
|
if (length(memory) == 0)
|
|
memory += msg
|
|
else
|
|
memory += "<BR>[msg]"
|
|
|
|
if (popup)
|
|
memory()
|
|
|
|
/mob/proc/update_flavor_text()
|
|
set src in usr
|
|
if(usr != src)
|
|
to_chat(usr, "No.")
|
|
var/msg = sanitize(input(usr,"Set the flavor text in your 'examine' verb.","Flavor Text",html_decode(flavor_text)) as message|null, extra = 0)
|
|
|
|
if(msg != null)
|
|
flavor_text = msg
|
|
|
|
/mob/proc/warn_flavor_changed()
|
|
if(flavor_text && flavor_text != "") // don't spam people that don't use it!
|
|
to_chat(src, "<h2 class='alert'>OOC Warning:</h2>")
|
|
to_chat(src, "<span class='alert'>Your flavor text is likely out of date! <a href='byond://?src=\ref[src];flavor_change=1'>Change</a></span>")
|
|
|
|
/mob/proc/print_flavor_text()
|
|
if (flavor_text && flavor_text != "")
|
|
var/msg = replacetext(flavor_text, "\n", " ")
|
|
if(length(msg) <= 40)
|
|
return "<font color=#4F49AF>[msg]</font>"
|
|
else
|
|
return "<font color=#4F49AF>[copytext_preserve_html(msg, 1, 37)]... <a href='byond://?src=\ref[src];flavor_more=1'>More...</font></a>"
|
|
|
|
/mob/proc/set_respawn_timer(var/time)
|
|
// Try to figure out what time to use
|
|
|
|
// Special cases, can never respawn
|
|
if(SSticker?.mode?.deny_respawn)
|
|
time = -1
|
|
else if(!config_legacy.abandon_allowed)
|
|
time = -1
|
|
else if(!config_legacy.respawn)
|
|
time = -1
|
|
|
|
// Special case for observing before game start
|
|
else if(SSticker?.current_state <= GAME_STATE_SETTING_UP)
|
|
time = 1 MINUTE
|
|
|
|
// Wasn't given a time, use the config time
|
|
else if(!time)
|
|
time = config_legacy.respawn_time
|
|
|
|
var/keytouse = ckey
|
|
// Try harder to find a key to use
|
|
if(!keytouse && key)
|
|
keytouse = ckey(key)
|
|
else if(!keytouse && mind?.ckey)
|
|
keytouse = mind.ckey
|
|
|
|
GLOB.respawn_timers[keytouse] = world.time + time
|
|
|
|
/mob/observer/dead/set_respawn_timer()
|
|
if(config_legacy.antag_hud_restricted && has_enabled_antagHUD)
|
|
..(-1)
|
|
else
|
|
return // Don't set it, no need
|
|
|
|
/**
|
|
* Allows you to respawn, abandoning your current mob
|
|
*
|
|
* This sends you back to the lobby creating a new dead mob
|
|
*
|
|
* Only works if flag/norespawn is allowed in config
|
|
*/
|
|
/mob/verb/abandon_mob()
|
|
set name = "Respawn"
|
|
set category = VERB_CATEGORY_OOC
|
|
set desc = "Return to the lobby."
|
|
|
|
// don't lose out on that sweet observer playtime
|
|
SSplaytime.queue_playtimes(client)
|
|
|
|
if(stat != DEAD)
|
|
to_chat(usr, SPAN_BOLDNOTICE("You must be dead to use this!"))
|
|
return
|
|
|
|
// Final chance to abort "respawning"
|
|
if(mind && timeofdeath) // They had spawned before
|
|
var/choice = alert(usr, "Returning to the menu will prevent your character from being revived in-round. Are you sure?", "Confirmation", "No, wait", "Yes, leave")
|
|
if(choice == "No, wait")
|
|
return
|
|
|
|
// Beyond this point, you're going to respawn
|
|
to_chat(usr, config_legacy.respawn_message)
|
|
|
|
if(!client)
|
|
log_game("[usr.key] AM failed due to disconnect.")
|
|
return
|
|
client.screen.Cut()
|
|
client.mob.reload_rendering()
|
|
if(!client)
|
|
log_game("[usr.key] AM failed due to disconnect.")
|
|
return
|
|
|
|
announce_ghost_joinleave(client, 0)
|
|
|
|
var/mob/new_player/M = new /mob/new_player()
|
|
if(!client)
|
|
log_game("[usr.key] AM failed due to disconnect.")
|
|
qdel(M)
|
|
return
|
|
|
|
transfer_client_to(M)
|
|
if(M.mind)
|
|
M.mind.reset()
|
|
return
|
|
|
|
/**
|
|
* Allows you to respawn, abandoning your current mob
|
|
*
|
|
* This sends you back to the lobby creating a new dead mob
|
|
*
|
|
* Doesn't require the config to be set.
|
|
*/
|
|
/mob/verb/return_to_menu()
|
|
set name = "Return to Menu"
|
|
set category = VERB_CATEGORY_OOC
|
|
set desc = "Return to the lobby."
|
|
return abandon_mob()
|
|
|
|
/mob/verb/observe()
|
|
set name = "Observe"
|
|
set category = VERB_CATEGORY_OOC
|
|
|
|
if(stat != DEAD || istype(src, /mob/new_player))
|
|
to_chat(usr, "<font color=#4F49AF>You must be observing to use this!</font>")
|
|
return
|
|
|
|
var/list/names = list()
|
|
var/list/namecounts = list()
|
|
var/list/creatures = list()
|
|
|
|
/*for(var/obj/O in world) //EWWWWWWWWWWWWWWWWWWWWWWWW ~needs to be optimised
|
|
if(!O.loc)
|
|
continue
|
|
if(istype(O, /obj/item/disk/nuclear))
|
|
var/name = "Nuclear Disk"
|
|
if (names.Find(name))
|
|
namecounts[name]++
|
|
name = "[name] ([namecounts[name]])"
|
|
else
|
|
names.Add(name)
|
|
namecounts[name] = 1
|
|
creatures[name] = O
|
|
|
|
if(istype(O, /obj/singularity))
|
|
var/name = "Singularity"
|
|
if (names.Find(name))
|
|
namecounts[name]++
|
|
name = "[name] ([namecounts[name]])"
|
|
else
|
|
names.Add(name)
|
|
namecounts[name] = 1
|
|
creatures[name] = O
|
|
*/
|
|
|
|
for(var/mob/M in sortList(GLOB.mob_list))
|
|
var/name = M.name
|
|
if (names.Find(name))
|
|
namecounts[name]++
|
|
name = "[name] ([namecounts[name]])"
|
|
else
|
|
names.Add(name)
|
|
namecounts[name] = 1
|
|
|
|
creatures[name] = M
|
|
|
|
|
|
var/eye_name = null
|
|
|
|
eye_name = input("Please, select a player!", "Observe", null, null) as null | anything in creatures
|
|
|
|
if (!eye_name)
|
|
return
|
|
|
|
var/mob/mob_eye = creatures[eye_name]
|
|
|
|
reset_perspective(mob_eye.get_perspective())
|
|
|
|
GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
|
|
|
|
//suppress the .click/dblclick macros so people can't use them to identify the location of items or aimbot
|
|
/mob/verb/DisClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
|
|
set name = ".click"
|
|
set hidden = TRUE
|
|
set category = null
|
|
if(GLOB.exploit_warn_spam_prevention < world.time)
|
|
var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .click macro!"
|
|
log_admin(msg)
|
|
message_admins(msg)
|
|
log_click("DROPPED: .click macro from [ckey] at [argu], [sec], [number1]. [number2]")
|
|
GLOB.exploit_warn_spam_prevention = world.time + 10
|
|
|
|
/mob/verb/DisDblClick(argu = null as anything, sec = "" as text, number1 = 0 as num , number2 = 0 as num)
|
|
set name = ".dblclick"
|
|
set hidden = TRUE
|
|
set category = null
|
|
if(GLOB.exploit_warn_spam_prevention < world.time)
|
|
var/msg = "[key_name_admin(src)]([ADMIN_KICK(src)]) attempted to use the .dblclick macro!"
|
|
log_admin(msg)
|
|
message_admins(msg)
|
|
log_click("DROPPED: .dblclick macro from [ckey] at [argu], [sec], [number1]. [number2]")
|
|
GLOB.exploit_warn_spam_prevention = world.time + 10
|
|
|
|
/**
|
|
* Topic call back for any mob
|
|
*
|
|
* * Unset machines if "mach_close" sent
|
|
* * refresh the inventory of machines in range if "refresh" sent
|
|
* * handles the strip panel equip and unequip as well if "item" sent
|
|
*/
|
|
/mob/Topic(href, href_list)
|
|
if(href_list["strip"])
|
|
var/op = href_list["strip"]
|
|
handle_strip_topic(usr, href_list, op)
|
|
return
|
|
if(href_list["mach_close"])
|
|
var/t1 = "window=[href_list["mach_close"]]"
|
|
unset_machine()
|
|
src << browse(null, t1)
|
|
|
|
if(href_list["flavor_more"])
|
|
usr << browse("<HTML><HEAD><TITLE>[name]</TITLE></HEAD><BODY><TT>[replacetext(flavor_text, "\n", "<BR>")]</TT></BODY></HTML>", "window=[name];size=500x200")
|
|
onclose(usr, "[name]")
|
|
if(href_list["flavor_change"])
|
|
update_flavor_text()
|
|
// ..()
|
|
return
|
|
|
|
|
|
/mob/proc/pull_damage()
|
|
if(ishuman(src))
|
|
var/mob/living/carbon/human/H = src
|
|
if(H.health - H.halloss <= H.getSoftCritHealth())
|
|
for(var/name in H.organs_by_name)
|
|
var/obj/item/organ/external/e = H.organs_by_name[name]
|
|
if(e && H.lying)
|
|
if((e.status & ORGAN_BROKEN && (!e.splinted || (e.splinted && (e.splinted in e.contents) && prob(30))) || e.status & ORGAN_BLEEDING) && (H.getBruteLoss() + H.getFireLoss() >= 100))
|
|
return 1
|
|
return 0
|
|
|
|
/mob/proc/can_use_hands()
|
|
return
|
|
|
|
/mob/proc/is_active()
|
|
return (0 >= usr.stat)
|
|
|
|
/mob/proc/is_dead()
|
|
return stat == DEAD
|
|
|
|
/mob/proc/is_mechanical()
|
|
if(mind && (mind.assigned_role == "Cyborg" || mind.assigned_role == "AI"))
|
|
return 1
|
|
return istype(src, /mob/living/silicon) || get_species_name() == "Machine"
|
|
|
|
/mob/proc/is_ready()
|
|
return client && !!mind
|
|
|
|
/mob/proc/get_gender()
|
|
return gender
|
|
|
|
/mob/proc/get_visible_gender()
|
|
return gender
|
|
|
|
/mob/proc/see(message)
|
|
if(!is_active())
|
|
return 0
|
|
to_chat(src, message)
|
|
return 1
|
|
|
|
/mob/proc/show_viewers(message)
|
|
for(var/mob/M in viewers())
|
|
M.see(message)
|
|
|
|
/// This might need a rename but it should replace the can this mob use things check
|
|
/mob/proc/IsAdvancedToolUser()
|
|
return 0
|
|
|
|
/mob/proc/AdjustLosebreath(amount)
|
|
losebreath = clamp(0, losebreath + amount, 25)
|
|
|
|
/mob/proc/SetLosebreath(amount)
|
|
losebreath = clamp(0, amount, 25)
|
|
|
|
/mob/proc/get_species_name()
|
|
return ""
|
|
|
|
/**
|
|
* DO NOT USE THIS
|
|
*
|
|
* this should be phased out for get_species_id().
|
|
*/
|
|
/mob/proc/get_true_species_name()
|
|
return ""
|
|
|
|
// todo: species vs subspecies
|
|
// /mob/proc/get_species_id()
|
|
// return
|
|
|
|
/mob/proc/flash_weak_pain()
|
|
flick("weak_pain",pain)
|
|
|
|
/mob/proc/get_visible_implants(var/class = 0)
|
|
var/list/visible_implants = list()
|
|
return visible_implants
|
|
|
|
// TODO: rework and readd embeds
|
|
|
|
// /mob/proc/yank_out_object()
|
|
// set category = VERB_CATEGORY_OBJECT
|
|
// set name = "Yank out object"
|
|
// set desc = "Remove an embedded item at the cost of bleeding and pain."
|
|
// set src in view(1)
|
|
|
|
// if(!isliving(usr) || !usr.canClick())
|
|
// return
|
|
// usr.setClickCooldownLegacy(20)
|
|
|
|
// if(usr.stat == 1)
|
|
// to_chat(usr, "You are unconcious and cannot do that!")
|
|
// return
|
|
|
|
// if(usr.restrained())
|
|
// to_chat(usr, "You are restrained and cannot do that!")
|
|
// return
|
|
|
|
// var/mob/S = src
|
|
// var/mob/U = usr
|
|
// var/list/valid_objects = list()
|
|
// var/self = null
|
|
|
|
// if(S == U)
|
|
// self = 1 // Removing object from yourself.
|
|
|
|
// valid_objects = get_visible_implants(0)
|
|
// if(!valid_objects.len)
|
|
// if(self)
|
|
// to_chat(src, "You have nothing stuck in your body that is large enough to remove.")
|
|
// else
|
|
// to_chat(U, "[src] has nothing stuck in their wounds that is large enough to remove.")
|
|
// return
|
|
|
|
// var/obj/item/selection = input("What do you want to yank out?", "Embedded objects") in valid_objects
|
|
|
|
// if(self)
|
|
// to_chat(src, "<span class='warning'>You attempt to get a good grip on [selection] in your body.</span>")
|
|
// else
|
|
// to_chat(U, "<span class='warning'>You attempt to get a good grip on [selection] in [S]'s body.</span>")
|
|
|
|
// if(!do_after(U, 30))
|
|
// return
|
|
// if(!selection || !S || !U)
|
|
// return
|
|
|
|
// if(self)
|
|
// visible_message("<span class='warning'><b>[src] rips [selection] out of their body.</b></span>","<span class='warning'><b>You rip [selection] out of your body.</b></span>")
|
|
// else
|
|
// visible_message("<span class='warning'><b>[usr] rips [selection] out of [src]'s body.</b></span>","<span class='warning'><b>[usr] rips [selection] out of your body.</b></span>")
|
|
// valid_objects = get_visible_implants(0)
|
|
// if(valid_objects.len == 1) //Yanking out last object - removing verb.
|
|
// remove_verb(src, /mob/proc/yank_out_object)
|
|
|
|
// if(ishuman(src))
|
|
// var/mob/living/carbon/human/H = src
|
|
// var/obj/item/organ/external/affected
|
|
|
|
// for(var/obj/item/organ/external/organ in H.organs) //Grab the organ holding the implant.
|
|
// for(var/obj/item/O in organ.implants)
|
|
// if(O == selection)
|
|
// affected = organ
|
|
|
|
// affected.implants -= selection
|
|
// H.shock_stage+=20
|
|
// affected.inflict_bodypart_damage(
|
|
// brute = selection.w_class * 3,
|
|
// damage_mode = DAMAGE_MODE_EDGE,
|
|
// weapon_descriptor = "object removal",
|
|
// )
|
|
|
|
// if(prob(selection.w_class * 5) && (affected.robotic < ORGAN_ROBOT)) //I'M SO ANEMIC I COULD JUST -DIE-.
|
|
// affected.create_specific_wound(/datum/wound/internal_bleeding, min(selection.w_class * 5, 15))
|
|
// H.custom_pain("Something tears wetly in your [affected] as [selection] is pulled free!", 50)
|
|
|
|
// if (ishuman(U))
|
|
// var/mob/living/carbon/human/human_user = U
|
|
// human_user.bloody_hands(H)
|
|
|
|
// else if(issilicon(src))
|
|
// var/mob/living/silicon/robot/R = src
|
|
// R.embedded -= selection
|
|
// R.adjustBruteLoss(5)
|
|
// R.adjustFireLoss(10)
|
|
|
|
// selection.forceMove(get_turf(src))
|
|
// U.put_in_hands(selection)
|
|
|
|
// for(var/obj/item/O in pinned)
|
|
// if(O == selection)
|
|
// pinned -= O
|
|
// if(!pinned.len)
|
|
// anchored = 0
|
|
// return 1
|
|
|
|
/// Check for brain worms in head.
|
|
/mob/proc/has_brain_worms()
|
|
|
|
for(var/I in contents)
|
|
if(istype(I,/mob/living/simple_mob/animal/borer))
|
|
return I
|
|
|
|
return 0
|
|
|
|
/mob/proc/updateicon()
|
|
return
|
|
|
|
/mob/verb/face_direction()
|
|
|
|
set name = "Face Direction"
|
|
set category = VERB_CATEGORY_IC
|
|
set src = usr
|
|
|
|
set_face_dir()
|
|
|
|
if(!facing_dir)
|
|
to_chat(usr, "You are now not facing anything.")
|
|
else
|
|
to_chat(usr, "You are now facing [dir2text(facing_dir)].")
|
|
|
|
/mob/proc/set_face_dir(newdir)
|
|
if(newdir)
|
|
if(newdir == facing_dir)
|
|
facing_dir = null
|
|
else
|
|
facing_dir = newdir
|
|
setDir(newdir)
|
|
else
|
|
if(facing_dir)
|
|
facing_dir = null
|
|
else
|
|
facing_dir = dir
|
|
|
|
/mob/setDir()
|
|
if(facing_dir)
|
|
if(!canface() || lying || buckled || restrained())
|
|
facing_dir = null
|
|
else if(dir != facing_dir)
|
|
return ..(facing_dir)
|
|
else
|
|
return ..()
|
|
|
|
/mob/verb/northfaceperm()
|
|
set hidden = 1
|
|
set src = usr
|
|
set_face_dir(client.client_dir(NORTH))
|
|
|
|
/mob/verb/southfaceperm()
|
|
set hidden = 1
|
|
set src = usr
|
|
set_face_dir(client.client_dir(SOUTH))
|
|
|
|
/mob/verb/eastfaceperm()
|
|
set hidden = 1
|
|
set src = usr
|
|
set_face_dir(client.client_dir(EAST))
|
|
|
|
/mob/verb/westfaceperm()
|
|
set hidden = 1
|
|
set src = usr
|
|
set_face_dir(client.client_dir(WEST))
|
|
|
|
/mob/proc/adjustEarDamage()
|
|
return
|
|
|
|
/mob/proc/setEarDamage()
|
|
return
|
|
|
|
/mob/proc/isSynthetic()
|
|
return 0
|
|
|
|
/mob/proc/is_muzzled()
|
|
return 0
|
|
|
|
//Exploitable Info Update
|
|
|
|
/mob/proc/amend_exploitable(var/obj/item/I)
|
|
if(istype(I))
|
|
exploit_addons |= I
|
|
var/exploitmsg = html_decode("\n" + "Has " + I.name + ".")
|
|
exploit_record += exploitmsg
|
|
|
|
/client/proc/check_has_body_select()
|
|
return mob && mob.hud_used && istype(mob.zone_sel, /atom/movable/screen/zone_sel)
|
|
|
|
/client/verb/body_toggle_head()
|
|
set name = "body-toggle-head"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_HEAD, O_EYES, O_MOUTH))
|
|
|
|
/client/verb/body_r_arm()
|
|
set name = "body-r-arm"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_R_ARM,BP_R_HAND))
|
|
|
|
/client/verb/body_l_arm()
|
|
set name = "body-l-arm"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_L_ARM,BP_L_HAND))
|
|
|
|
/client/verb/body_chest()
|
|
set name = "body-chest"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_TORSO))
|
|
|
|
/client/verb/body_groin()
|
|
set name = "body-groin"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_GROIN))
|
|
|
|
/client/verb/body_r_leg()
|
|
set name = "body-r-leg"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_R_LEG,BP_R_FOOT))
|
|
|
|
/client/verb/body_l_leg()
|
|
set name = "body-l-leg"
|
|
set hidden = 1
|
|
toggle_zone_sel(list(BP_L_LEG,BP_L_FOOT))
|
|
|
|
/client/proc/toggle_zone_sel(list/zones)
|
|
if(!check_has_body_select())
|
|
return
|
|
var/atom/movable/screen/zone_sel/selector = mob.zone_sel
|
|
selector.set_selected_zone(next_list_item(mob.zone_sel.selecting,zones))
|
|
|
|
// This handles setting the client's color variable, which makes everything look a specific color.
|
|
// This proc is here so it can be called without needing to check if the client exists, or if the client relogs.
|
|
// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself.
|
|
/mob/proc/update_client_color()
|
|
if(client && client.color)
|
|
animate(client, color = null, time = 10)
|
|
return
|
|
|
|
/mob/proc/will_show_tooltip()
|
|
if(alpha <= EFFECTIVE_INVIS)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/mob/MouseEntered(location, control, params)
|
|
if(usr != src && usr.get_preference_toggle(/datum/game_preference_toggle/game/mob_tooltips) && src.will_show_tooltip())
|
|
openToolTip(user = usr, tip_src = src, params = params, title = get_nametag_name(usr), content = get_nametag_desc(usr))
|
|
|
|
..()
|
|
|
|
/mob/MouseDown()
|
|
closeToolTip(usr) //No reason not to, really
|
|
|
|
..()
|
|
|
|
/mob/MouseExited()
|
|
closeToolTip(usr) //No reason not to, really
|
|
|
|
..()
|
|
|
|
// Manages a global list of mobs with clients attached, indexed by z-level.
|
|
/mob/proc/update_client_z(new_z) // +1 to register, null to unregister.
|
|
if(registered_z != new_z)
|
|
if(registered_z)
|
|
GLOB.players_by_zlevel[registered_z] -= src
|
|
if(client)
|
|
if(new_z)
|
|
GLOB.players_by_zlevel[new_z] += src
|
|
registered_z = new_z
|
|
else
|
|
registered_z = null
|
|
|
|
/mob/on_changed_z_level(old_z, new_z)
|
|
..()
|
|
update_client_z(new_z)
|
|
|
|
/mob/verb/local_diceroll(n as num)
|
|
set name = "diceroll"
|
|
set category = VERB_CATEGORY_OOC
|
|
set desc = "Roll a random number between 1 and a chosen number."
|
|
set src = usr
|
|
|
|
n = round(n) // why are you putting in floats??
|
|
if(n < 2)
|
|
to_chat(src, "<span class='warning'>[n] must be 2 or above, otherwise why are you rolling?</span>")
|
|
return
|
|
|
|
to_chat(src, "<span class='notice'>Diceroll result: <b>[rand(1, n)]</b></span>")
|
|
|
|
/**
|
|
* Checks for anti magic sources.
|
|
*
|
|
* @params
|
|
* - magic - wizard-type magic
|
|
* - holy - cult-type magic, stuff chaplains/nullrods/similar should be countering
|
|
* - chargecost - charges to remove from antimagic if applicable/not a permanent source
|
|
* - self - check if the antimagic is ourselves
|
|
*
|
|
* @return The datum source of the antimagic
|
|
*/
|
|
/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, chargecost = 1, self = FALSE)
|
|
if(!magic && !holy)
|
|
return
|
|
var/list/protection_sources = list()
|
|
if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, src, magic, holy, chargecost, self, protection_sources) & COMPONENT_MAGIC_BLOCKED)
|
|
if(protection_sources.len)
|
|
return pick(protection_sources)
|
|
else
|
|
return src
|
|
if((magic && HAS_TRAIT(src, TRAIT_ANTIMAGIC)) || (holy && HAS_TRAIT(src, TRAIT_HOLY)))
|
|
return src
|
|
|
|
/mob/drop_location()
|
|
if(temporary_form)
|
|
return temporary_form.drop_location()
|
|
return ..()
|
|
|
|
/**
|
|
* Returns whether or not we should be allowed to examine a target
|
|
*/
|
|
/mob/proc/allow_examine(atom/A)
|
|
return (client?.eye == src) || (A in DirectAccess())
|
|
|
|
/// Checks for slots that are currently obscured by other garments.
|
|
/mob/proc/check_obscured_slots()
|
|
return
|
|
|
|
/mob/z_pass_in(atom/movable/AM, dir, turf/old_loc)
|
|
return TRUE // we don't block
|
|
|
|
/mob/z_pass_out(atom/movable/AM, dir, turf/new_loc)
|
|
return TRUE
|
|
|
|
//* Dir *//
|
|
|
|
/**
|
|
* Tries to turn to face an atom.
|
|
*/
|
|
/mob/proc/face_atom(var/atom/atom_to_face)
|
|
if(buckled || stat != CONSCIOUS || !atom_to_face || !x || !y || !atom_to_face.x || !atom_to_face.y)
|
|
return
|
|
if(!CHECK_MOBILITY(src, MOBILITY_CAN_MOVE))
|
|
return
|
|
|
|
var/dx = atom_to_face.x - x
|
|
var/dy = atom_to_face.y - y
|
|
if(!dx && !dy) // Wall items are graphically shifted but on the floor
|
|
if(atom_to_face.pixel_y > 16)
|
|
setDir(NORTH)
|
|
else if(atom_to_face.pixel_y < -16)
|
|
setDir(SOUTH)
|
|
else if(atom_to_face.pixel_x > 16)
|
|
setDir(EAST)
|
|
else if(atom_to_face.pixel_x < -16)
|
|
setDir(WEST)
|
|
return
|
|
|
|
if(abs(dx) < abs(dy))
|
|
if(dy > 0)
|
|
setDir(NORTH)
|
|
else
|
|
setDir(SOUTH)
|
|
else
|
|
if(dx > 0)
|
|
setDir(EAST)
|
|
else
|
|
setDir(WEST)
|
|
|
|
//! Misc
|
|
/**
|
|
* Whether the mob can use Topic to interact with machines
|
|
*
|
|
* Args:
|
|
* be_close - Whether you need to be next to/on top of M
|
|
* no_dexterity - Whether you need to be an ADVANCEDTOOLUSER
|
|
* no_tk - If be_close is TRUE, this will block Telekinesis from bypassing the requirement
|
|
* need_hands - Whether you need hands to use this
|
|
* floor_okay - Whether mobility flags should be checked for MOBILITY_CAN_UI to use.
|
|
*/
|
|
/mob/proc/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE)
|
|
return
|
|
|
|
/**
|
|
* Checks if we can avoid things like landmine, lava, etc, whether beneficial or harmful.
|
|
*/
|
|
/mob/is_avoiding_ground()
|
|
return ..() || hovering || flying || (buckled?.buckle_flags & BUCKLING_GROUND_HOIST) || buckled?.is_avoiding_ground()
|
|
|
|
//* Layer-shift verbs *//
|
|
|
|
/mob/proc/get_relative_shift_targets()
|
|
. = list()
|
|
var/us = isnull(base_layer)? layer : base_layer
|
|
for(var/mob/M in range(1, src))
|
|
if(M.plane != plane)
|
|
continue
|
|
if(us == (isnull(M.base_layer)? M.layer : M.base_layer))
|
|
. += M
|
|
. -= src
|
|
|
|
//* Abilities *//
|
|
|
|
/mob/proc/init_abilities()
|
|
var/list/built = list()
|
|
var/list/registering = list()
|
|
for(var/datum/ability/ability_path as anything in abilities)
|
|
if(istype(ability_path))
|
|
built += ability_path // don't re-associate existing ones.
|
|
else if(ispath(ability_path, /datum/ability))
|
|
registering += new ability_path
|
|
abilities = built
|
|
for(var/datum/ability/ability as anything in registering)
|
|
ability.associate(src)
|
|
|
|
/mob/proc/dispose_abilities()
|
|
for(var/datum/ability/ability in abilities)
|
|
ability.disassociate(src)
|
|
abilities = null
|
|
|
|
/**
|
|
* mob side registration of abilities. must be called from /datum/ability/proc/associate!
|
|
*/
|
|
/mob/proc/register_ability(datum/ability/ability)
|
|
LAZYINITLIST(abilities)
|
|
abilities += ability
|
|
|
|
/**
|
|
* mob side unregistration of abilities. must be called from /datum/ability/proc/disassociate!
|
|
*/
|
|
/mob/proc/unregister_ability(datum/ability/ability)
|
|
LAZYREMOVE(abilities, ability)
|
|
|
|
//* Atom HUDs *//
|
|
|
|
/**
|
|
* Initializes all atom HUDs for us
|
|
* todo: this should be atom level but uhh lmao lol don't call it on atom/Initialize() either
|
|
*/
|
|
/mob/proc/prepare_huds()
|
|
if(!atom_huds_to_initialize)
|
|
return
|
|
for(var/hud in atom_huds_to_initialize)
|
|
update_atom_hud_provider(src, hud)
|
|
atom_huds_to_initialize = null
|
|
|
|
//* Radioactivity *//
|
|
|
|
/mob/clean_radiation(str, mul, cheap)
|
|
. = ..()
|
|
if(cheap)
|
|
return
|
|
for(var/obj/item/I as anything in get_equipped_items(TRUE, TRUE))
|
|
I.clean_radiation(str, mul, cheap)
|
|
|
|
//* Reachability *//
|
|
|
|
/mob/CanReachOut(atom/movable/mover, atom/target, obj/item/tool, list/cache)
|
|
return FALSE
|
|
|
|
/mob/CanReachIn(atom/movable/mover, atom/target, obj/item/tool, list/cache)
|
|
return FALSE
|
|
|
|
//* Pixel Offsets *//
|
|
|
|
/mob/proc/get_buckled_pixel_x_offset()
|
|
if(!buckled)
|
|
return 0
|
|
// todo: this doesn't properly take into account all transforms of both us and the buckled object
|
|
. = get_centering_pixel_x_offset(dir)
|
|
if(lying != 0)
|
|
. *= cos(lying)
|
|
. += sin(lying) * get_centering_pixel_y_offset(dir)
|
|
return buckled.pixel_x + . - buckled.get_centering_pixel_x_offset(buckled.dir) + buckled.get_buckled_x_offset(src)
|
|
|
|
/mob/proc/get_buckled_pixel_y_offset()
|
|
if(!buckled)
|
|
return 0
|
|
// todo: this doesn't properly take into account all transforms of both us and the buckled object
|
|
. = get_centering_pixel_y_offset(dir)
|
|
if(lying != 0)
|
|
. *= cos(lying)
|
|
. += sin(lying) * get_centering_pixel_x_offset(dir)
|
|
return buckled.pixel_y + . - buckled.get_centering_pixel_y_offset(buckled.dir) + buckled.get_buckled_y_offset(src)
|
|
|
|
/mob/get_managed_pixel_x()
|
|
return ..() + shift_pixel_x + get_buckled_pixel_x_offset()
|
|
|
|
/mob/get_managed_pixel_y()
|
|
return ..() + shift_pixel_y + get_buckled_pixel_y_offset()
|
|
|
|
/mob/get_centering_pixel_x_offset(dir)
|
|
. = ..()
|
|
. += shift_pixel_x
|
|
|
|
/mob/get_centering_pixel_y_offset(dir)
|
|
. = ..()
|
|
. += shift_pixel_y
|
|
|
|
/mob/proc/reset_pixel_shifting()
|
|
if(!shifted_pixels)
|
|
return
|
|
shifted_pixels = FALSE
|
|
pixel_x -= shift_pixel_x
|
|
pixel_y -= shift_pixel_y
|
|
wallflowering = NONE
|
|
shift_pixel_x = 0
|
|
shift_pixel_y = 0
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_PIXEL_OFFSET_CHANGED)
|
|
|
|
/mob/proc/set_pixel_shift_x(val)
|
|
if(!val)
|
|
return
|
|
shifted_pixels = TRUE
|
|
pixel_x += (val - shift_pixel_x)
|
|
shift_pixel_x = val
|
|
switch(val)
|
|
if(-INFINITY to -WALLFLOWERING_PIXEL_SHIFT)
|
|
wallflowering = (wallflowering & ~(EAST)) | WEST
|
|
if(-WALLFLOWERING_PIXEL_SHIFT + 1 to WALLFLOWERING_PIXEL_SHIFT - 1)
|
|
wallflowering &= ~(EAST|WEST)
|
|
if(WALLFLOWERING_PIXEL_SHIFT to INFINITY)
|
|
wallflowering = (wallflowering & ~(WEST)) | EAST
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_PIXEL_OFFSET_CHANGED)
|
|
|
|
/mob/proc/set_pixel_shift_y(val)
|
|
if(!val)
|
|
return
|
|
shifted_pixels = TRUE
|
|
pixel_y += (val - shift_pixel_y)
|
|
shift_pixel_y = val
|
|
switch(val)
|
|
if(-INFINITY to -WALLFLOWERING_PIXEL_SHIFT)
|
|
wallflowering = (wallflowering & ~(NORTH)) | SOUTH
|
|
if(-WALLFLOWERING_PIXEL_SHIFT + 1 to WALLFLOWERING_PIXEL_SHIFT - 1)
|
|
wallflowering &= ~(NORTH|SOUTH)
|
|
if(WALLFLOWERING_PIXEL_SHIFT to INFINITY)
|
|
wallflowering = (wallflowering & ~(SOUTH)) | NORTH
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_PIXEL_OFFSET_CHANGED)
|
|
|
|
/mob/proc/adjust_pixel_shift_x(val)
|
|
if(!val)
|
|
return
|
|
shifted_pixels = TRUE
|
|
shift_pixel_x += val
|
|
pixel_x += val
|
|
switch(shift_pixel_x)
|
|
if(-INFINITY to -WALLFLOWERING_PIXEL_SHIFT)
|
|
wallflowering = (wallflowering & ~(EAST)) | WEST
|
|
if(-WALLFLOWERING_PIXEL_SHIFT + 1 to WALLFLOWERING_PIXEL_SHIFT - 1)
|
|
wallflowering &= ~(EAST|WEST)
|
|
if(WALLFLOWERING_PIXEL_SHIFT to INFINITY)
|
|
wallflowering = (wallflowering & ~(WEST)) | EAST
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_PIXEL_OFFSET_CHANGED)
|
|
|
|
/mob/proc/adjust_pixel_shift_y(val)
|
|
if(!val)
|
|
return
|
|
shifted_pixels = TRUE
|
|
shift_pixel_y += val
|
|
pixel_y += val
|
|
switch(shift_pixel_y)
|
|
if(-INFINITY to -WALLFLOWERING_PIXEL_SHIFT)
|
|
wallflowering = (wallflowering & ~(NORTH)) | SOUTH
|
|
if(-WALLFLOWERING_PIXEL_SHIFT + 1 to WALLFLOWERING_PIXEL_SHIFT - 1)
|
|
wallflowering &= ~(NORTH|SOUTH)
|
|
if(WALLFLOWERING_PIXEL_SHIFT to INFINITY)
|
|
wallflowering = (wallflowering & ~(SOUTH)) | NORTH
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_PIXEL_OFFSET_CHANGED)
|
|
|
|
///Can the mob interact() with an atom?
|
|
/mob/proc/can_interact_with(atom/A, treat_mob_as_adjacent)
|
|
if(isAdminGhostAI(src))
|
|
return TRUE
|
|
//Return early. we do not need to check that we are on adjacent turfs (i.e we are inside a closet)
|
|
if (treat_mob_as_adjacent && src == A.loc)
|
|
return TRUE
|
|
if (Adjacent(A))
|
|
return TRUE
|
|
if((MUTATION_TELEKINESIS in mutations) && (get_dist(src, A) > tk_maxrange))
|
|
return TRUE
|
|
|
|
//range check
|
|
if(!interaction_range) // If you don't have extra length, GO AWAY
|
|
return FALSE
|
|
var/turf/our_turf = get_turf(src)
|
|
var/turf/their_turf = get_turf(A)
|
|
if (!our_turf || !their_turf)
|
|
return FALSE
|
|
return ISINRANGE(their_turf.x, our_turf.x - interaction_range, our_turf.x + interaction_range) && ISINRANGE(their_turf.y, our_turf.y - interaction_range, our_turf.y + interaction_range)
|