Files
Bubberstation/code/modules/mob/mob.dm

1286 lines
37 KiB
Plaintext

/**
* Delete a mob
*
* Removes mob from the following global lists
* * GLOB.mob_list
* * GLOB.dead_mob_list
* * GLOB.alive_mob_list
* * GLOB.all_clockwork_mobs
* * GLOB.mob_directory
*
* Unsets the focus var
*
* Clears alerts for this mob
*
* Resets all the observers perspectives to the tile this mob is on
*
* qdels any client colours in place on this mob
*
* Ghostizes the client attached to this mob
*
* Parent call
*/
/mob/Destroy()//This makes sure that mobs with clients/keys are not just deleted from the game.
remove_from_mob_list()
remove_from_dead_mob_list()
remove_from_alive_mob_list()
focus = null
if(length(progressbars))
stack_trace("[src] destroyed with elements in its progressbars list")
progressbars = null
for (var/alert in alerts)
clear_alert(alert, TRUE)
if(observers && observers.len)
for(var/M in observers)
var/mob/dead/observe = M
observe.reset_perspective(null)
qdel(hud_used)
QDEL_LIST(client_colours)
ghostize()
return ..()
/**
* Intialize a mob
*
* Sends global signal COMSIG_GLOB_MOB_CREATED
*
* Adds to global lists
* * GLOB.mob_list
* * GLOB.mob_directory (by tag)
* * GLOB.dead_mob_list - if mob is dead
* * GLOB.alive_mob_list - if the mob is alive
*
* Other stuff:
* * Sets the mob focus to itself
* * Generates huds
* * If there are any global alternate apperances apply them to this mob
* * set a random nutrition level
* * Intialize the movespeed of the mob
*/
/mob/Initialize()
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_CREATED, src)
add_to_mob_list()
if(stat == DEAD)
add_to_dead_mob_list()
else
add_to_alive_mob_list()
set_focus(src)
prepare_huds()
for(var/v in GLOB.active_alternate_appearances)
if(!v)
continue
var/datum/atom_hud/alternate_appearance/AA = v
AA.onNewMob(src)
set_nutrition(rand(NUTRITION_LEVEL_START_MIN, NUTRITION_LEVEL_START_MAX))
. = ..()
update_config_movespeed()
update_movespeed(TRUE)
/**
* Generate the tag for this mob
*
* This is simply "mob_"+ a global incrementing counter that goes up for every mob
*/
/mob/GenerateTag()
tag = "mob_[next_mob_id++]"
/**
* Prepare the huds for this atom
*
* Goes through hud_possible list and adds the images to the hud_list variable (if not already
* cached)
*/
/atom/proc/prepare_huds()
hud_list = list()
for(var/hud in hud_possible)
var/hint = hud_possible[hud]
switch(hint)
if(HUD_LIST_LIST)
hud_list[hud] = list()
else
var/image/I = image('icons/mob/hud.dmi', src, "")
I.appearance_flags = RESET_COLOR|RESET_TRANSFORM
hud_list[hud] = I
/**
* Some kind of debug verb that gives atmosphere environment details
*/
/mob/proc/Cell()
set category = "Admin"
set hidden = 1
if(!loc)
return 0
var/datum/gas_mixture/environment = loc.return_air()
var/t = "<span class='notice'>Coordinates: [x],[y] \n</span>"
t += "<span class='danger'>Temperature: [environment.temperature] \n</span>"
for(var/id in environment.gases)
var/gas = environment.gases[id]
if(gas[MOLES])
t+="<span class='notice'>[gas[GAS_META][META_GAS_NAME]]: [gas[MOLES]] \n</span>"
to_chat(usr, t)
/**
* Return the desc of this mob for a photo
*/
/mob/proc/get_photo_description(obj/item/camera/camera)
return "a ... thing?"
/**
* Show a message to this mob (visual or audible)
*/
/mob/proc/show_message(msg, type, alt_msg, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
if(!client)
return
msg = copytext_char(msg, 1, MAX_MESSAGE_LEN)
if(type)
if(type & MSG_VISUAL && is_blind() )//Vision related
if(!alt_msg)
return
else
msg = alt_msg
type = alt_type
if(type & MSG_AUDIBLE && !can_hear())//Hearing related
if(!alt_msg)
return
else
msg = alt_msg
type = alt_type
if(type & MSG_VISUAL && is_blind())
return
// voice muffling
if(stat == UNCONSCIOUS)
if(type & MSG_AUDIBLE) //audio
to_chat(src, "<I>... You can almost hear something ...</I>")
return
to_chat(src, msg)
/**
* Generate a visible message from this atom
*
* Show a message to all player mobs who sees this atom
*
* Show a message to the src mob (if the src is a mob)
*
* Use for atoms performing visible actions
*
* message is output to anyone who can see, e.g. "The [src] does something!"
*
* Vars:
* * self_message (optional) is what the src mob sees e.g. "You do something!"
* * blind_message (optional) is what blind people will hear e.g. "You hear something!"
* * vision_distance (optional) define how many tiles away the message can be seen.
* * ignored_mob (optional) doesn't show any message to a given mob if TRUE.
*/
/atom/proc/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
var/turf/T = get_turf(src)
if(!T)
return
if(!islist(ignored_mobs))
ignored_mobs = list(ignored_mobs)
var/list/hearers = get_hearers_in_view(vision_distance, src) //caches the hearers and then removes ignored mobs.
hearers -= ignored_mobs
if(self_message)
hearers -= src
for(var/mob/M in hearers)
if(!M.client)
continue
//This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
if(M.see_invisible < invisibility)//if src is invisible to M
msg = blind_message
else if(T != loc && T != src) //if src is inside something and not a turf.
msg = blind_message
else if(T.lighting_object && T.lighting_object.invisibility <= M.see_invisible && T.is_softly_lit()) //if it is too dark.
msg = blind_message
if(!msg)
continue
M.show_message(msg, MSG_VISUAL, blind_message, MSG_AUDIBLE)
///Adds the functionality to self_message.
/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
. = ..()
if(self_message)
show_message(self_message, MSG_VISUAL, blind_message, MSG_AUDIBLE)
/**
* Show a message to all mobs in earshot of this atom
*
* Use for objects performing audible actions
*
* vars:
* * message is the message output to anyone who can hear.
* * deaf_message (optional) is what deaf people will see.
* * hearing_distance (optional) is the range, how many tiles away the message can be heard.
*/
/atom/proc/audible_message(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message)
var/list/hearers = get_hearers_in_view(hearing_distance, src)
if(self_message)
hearers -= src
for(var/mob/M in hearers)
M.show_message(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
/**
* 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(message, deaf_message, hearing_distance = DEFAULT_MESSAGE_RANGE, self_message)
. = ..()
if(self_message)
show_message(self_message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
///Get the item on the mob in the storage slot identified by the id passed in
/mob/proc/get_item_by_slot(slot_id)
return null
///Is the mob restrained
/mob/proc/restrained(ignore_grab)
return
///Is the mob incapacitated
/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE)
return
/**
* This proc is called whenever someone clicks an inventory ui slot.
*
* Mostly tries to put the item into the slot if possible, or call attack hand
* on the item in the slot if the users active hand is empty
*/
/mob/proc/attack_ui(slot)
var/obj/item/W = get_active_held_item()
if(istype(W))
if(equip_to_slot_if_possible(W, slot,0,0,0))
return 1
if(!W)
// Activate the item
var/obj/item/I = get_item_by_slot(slot)
if(istype(I))
I.attack_hand(src)
return 0
/**
* Try to equip an item to a slot on the mob
*
* This is a SAFE proc. Use this instead of equip_to_slot()!
*
* set qdel_on_fail to have it delete W if it fails to equip
*
* set disable_warning to disable the 'you are unable to equip that' warning.
*
* unset redraw_mob to prevent the mob icons from being redrawn at the end.
*
* Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
*/
/mob/proc/equip_to_slot_if_possible(obj/item/W, slot, qdel_on_fail = FALSE, disable_warning = FALSE, redraw_mob = TRUE, bypass_equip_delay_self = FALSE, initial = FALSE, swap = FALSE)
if(!istype(W))
return FALSE
if(!W.mob_can_equip(src, null, slot, disable_warning, bypass_equip_delay_self, swap))
if(qdel_on_fail)
qdel(W)
else
if(!disable_warning)
to_chat(src, "<span class='warning'>You are unable to equip that!</span>")
return FALSE
equip_to_slot(W, slot, initial, redraw_mob, swap) //This proc should not ever fail.
return TRUE
/**
* Actually equips an item to a slot (UNSAFE)
*
* This is an UNSAFE proc. It merely handles the actual job of equipping. All the checks on
* whether you can or can't equip need to be done before! Use mob_can_equip() for that task.
*
*In most cases you will want to use equip_to_slot_if_possible()
*/
/mob/proc/equip_to_slot(obj/item/W, slot)
return
/**
* Equip an item to the slot or delete
*
* This is just a commonly used configuration for the equip_to_slot_if_possible() proc, used to
* equip people when the round starts and when events happen and such.
*
* Also bypasses equip delay checks, since the mob isn't actually putting it on.
* Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
*/
/mob/proc/equip_to_slot_or_del(obj/item/W, slot, initial = FALSE)
return equip_to_slot_if_possible(W, slot, TRUE, TRUE, FALSE, TRUE, initial)
/**
* Auto equip the passed in item the appropriate slot based on equipment priority
*
* puts the item "W" into an appropriate slot in a human's inventory
*
* returns 0 if it cannot, 1 if successful
*/
/mob/proc/equip_to_appropriate_slot(obj/item/W, swap=FALSE)
if(!istype(W))
return 0
var/slot_priority = W.slot_equipment_priority
if(!slot_priority)
slot_priority = list( \
ITEM_SLOT_BACK, ITEM_SLOT_ID,\
ITEM_SLOT_ICLOTHING, ITEM_SLOT_OCLOTHING,\
ITEM_SLOT_MASK, ITEM_SLOT_HEAD, ITEM_SLOT_NECK,\
ITEM_SLOT_FEET, ITEM_SLOT_GLOVES,\
ITEM_SLOT_EARS, ITEM_SLOT_EYES,\
ITEM_SLOT_BELT, ITEM_SLOT_SUITSTORE,\
ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET,\
ITEM_SLOT_DEX_STORAGE\
)
for(var/slot in slot_priority)
if(equip_to_slot_if_possible(W, slot, FALSE, TRUE, TRUE, FALSE, FALSE, swap)) //qdel_on_fail = FALSE; disable_warning = TRUE; redraw_mob = TRUE;
return 1
return 0
/**
* Reset the attached clients perspective (viewpoint)
*
* reset_perspective() set eye to common default : mob on turf, loc otherwise
* reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
*/
/mob/proc/reset_perspective(atom/A)
if(client)
if(A)
if(ismovable(A))
//Set the the thing unless it's us
if(A != src)
client.perspective = EYE_PERSPECTIVE
client.eye = A
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else if(isturf(A))
//Set to the turf unless it's our current turf
if(A != loc)
client.perspective = EYE_PERSPECTIVE
client.eye = A
else
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
//Do nothing
else
//Reset to common defaults: mob if on turf, otherwise current loc
if(isturf(loc))
client.eye = client.mob
client.perspective = MOB_PERSPECTIVE
else
client.perspective = EYE_PERSPECTIVE
client.eye = loc
return 1
/// Show the mob's inventory to another mob
/mob/proc/show_inv(mob/user)
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 = "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())
to_chat(src, "<span class='warning'>Something is there but you can't see it!</span>")
return
face_atom(A)
var/list/result
if(client)
LAZYINITLIST(client.recent_examines)
if(isnull(client.recent_examines[A]) || client.recent_examines[A] < world.time) // originally this wasn't an assoc list, but sometimes the timer failed and atoms stayed in a client's recent_examines, so we check here manually
client.recent_examines[A] = world.time + EXAMINE_MORE_TIME
result = A.examine(src)
addtimer(CALLBACK(src, .proc/clear_from_recent_examines, A), EXAMINE_MORE_TIME)
else
result = A.examine_more(src)
else
result = A.examine(src) // if a tree is examined but no client is there to see it, did the tree ever really exist?
to_chat(src, result.Join("\n"))
SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, A)
/mob/proc/clear_from_recent_examines(atom/A)
if(QDELETED(A) || !client)
return
LAZYREMOVE(client.recent_examines, A)
/**
* 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/dead/observer 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 = "Object"
if(!src || !isturf(src.loc))
return FALSE
if(client && !(A in view(client.view, src)))
return FALSE
if(istype(A, /obj/effect/temp_visual/point))
return FALSE
var/turf/tile = get_turf(A)
if (!tile)
return FALSE
var/turf/our_tile = get_turf(src)
var/obj/visual = new /obj/effect/temp_visual/point(our_tile, invisibility)
animate(visual, pixel_x = (tile.x - our_tile.x) * world.icon_size + A.pixel_x, pixel_y = (tile.y - our_tile.y) * world.icon_size + A.pixel_y, time = 1.7, easing = EASE_OUT)
return TRUE
///Can this mob resist (default FALSE)
/mob/proc/can_resist()
return FALSE //overridden in living.dm
///Spin this mob around it's central axis
/mob/proc/spin(spintime, speed)
set waitfor = 0
var/D = dir
if((spintime < 1)||(speed < 1)||!spintime||!speed)
return
while(spintime >= speed)
sleep(speed)
switch(D)
if(NORTH)
D = EAST
if(SOUTH)
D = WEST
if(EAST)
D = SOUTH
if(WEST)
D = NORTH
setDir(D)
spintime -= speed
///Update the pulling hud icon
/mob/proc/update_pull_hud_icon()
hud_used?.pull_icon?.update_icon()
///Update the resting hud icon
/mob/proc/update_rest_hud_icon()
hud_used?.rest_icon?.update_icon()
/**
* Verb to activate the object in your held hand
*
* Calls attack self on the item and updates the inventory hud for hands
*/
/mob/verb/mode()
set name = "Activate Held Object"
set category = "Object"
set src = usr
if(ismecha(loc))
return
if(incapacitated())
return
var/obj/item/I = get_active_held_item()
if(I)
I.attack_self(src)
update_inv_hands()
/**
* Get the notes of this mob
*
* This actually gets the mind datums notes
*/
/mob/verb/memory()
set name = "Notes"
set category = "IC"
set desc = "View your character's notes memory."
if(mind)
mind.show_memory(src)
else
to_chat(src, "You don't have a mind datum for some reason, so you can't look at your notes, if you had any.")
/**
* Add a note to the mind datum
*/
/mob/verb/add_memory(msg as message)
set name = "Add Note"
set category = "IC"
if(mind)
if (world.time < memory_throttle_time)
return
memory_throttle_time = world.time + 5 SECONDS
msg = copytext_char(msg, 1, MAX_MESSAGE_LEN)
msg = sanitize(msg)
mind.store_memory(msg)
else
to_chat(src, "You don't have a mind datum for some reason, so you can't add a note to it.")
/**
* 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 = "OOC"
if (CONFIG_GET(flag/norespawn))
return
if ((stat != DEAD || !( SSticker )))
to_chat(usr, "<span class='boldnotice'>You must be dead to use this!</span>")
return
log_game("[key_name(usr)] used abandon mob.")
to_chat(usr, "<span class='boldnotice'>Please roleplay correctly!</span>")
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
return
client.screen.Cut()
client.screen += client.void
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
return
var/mob/dead/new_player/M = new /mob/dead/new_player()
if(!client)
log_game("[key_name(usr)] AM failed due to disconnect.")
qdel(M)
return
M.key = key
// M.Login() //wat
return
/**
* Sometimes helps if the user is stuck in another perspective or camera
*/
/mob/verb/cancel_camera()
set name = "Cancel Camera View"
set category = "OOC"
reset_perspective(null)
unset_machine()
//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
return
/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
return
/**
* 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)
var/mob/user = usr
if(href_list["mach_close"])
var/t1 = text("window=[href_list["mach_close"]]")
unset_machine()
src << browse(null, t1)
if(user != src)
if(href_list["item"] && user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY))
var/slot = text2num(href_list["item"])
var/hand_index = text2num(href_list["hand_index"])
var/obj/item/what
if(hand_index)
what = get_item_for_held_index(hand_index)
slot = list(slot,hand_index)
else
what = get_item_by_slot(slot)
if(what)
if(!(what.item_flags & ABSTRACT))
user.stripPanelUnequip(what,src,slot)
else
user.stripPanelEquip(what,src,slot)
if(user.machine == src)
if(Adjacent(user))
show_inv(user)
else
user << browse(null,"window=mob[REF(src)]")
// The src mob is trying to strip an item from someone
// Defined in living.dm
/mob/proc/stripPanelUnequip(obj/item/what, mob/who)
return
// The src mob is trying to place an item on someone
// Defined in living.dm
/mob/proc/stripPanelEquip(obj/item/what, mob/who)
return
/**
* Controls if a mouse drop succeeds (return null if it doesnt)
*/
/mob/MouseDrop(mob/M)
. = ..()
if(M != usr)
return
if(usr == src)
return
if(!Adjacent(usr))
return
if(isAI(M))
return
/**
* Handle the result of a click drag onto this mob
*
* For mobs this just shows the inventory
*/
/mob/MouseDrop_T(atom/dropping, atom/user)
. = ..()
if(ismob(dropping) && dropping != user)
var/mob/M = dropping
if(ismob(user))
var/mob/U = user
if(!iscyborg(U) || U.a_intent == INTENT_HARM)
M.show_inv(U)
else
M.show_inv(user)
///Is the mob muzzled (default false)
/mob/proc/is_muzzled()
return 0
/**
* Output an update to the stat panel for the client
*
* calculates client ping, round id, server time, time dilation and other data about the round
* and puts it in the mob status panel on a regular loop
*/
/mob/Stat()
..()
if(statpanel("Status"))
if (client)
stat(null, "Ping: [round(client.lastping, 1)]ms (Average: [round(client.avgping, 1)]ms)")
stat(null, "Map: [SSmapping.config?.map_name || "Loading..."]")
var/datum/map_config/cached = SSmapping.next_map_config
if(cached)
stat(null, "Next Map: [cached.map_name]")
stat(null, "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]")
stat(null, "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]")
if (SSticker.round_start_time)
stat(null, "Round Time: [gameTimestamp("hh:mm:ss", (world.time - SSticker.round_start_time))]")
else
stat(null, "Lobby Time: [gameTimestamp("hh:mm:ss", 0)]")
stat(null, "Station Time: [station_time_timestamp()]")
stat(null, "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)")
if(SSshuttle.emergency)
var/ETA = SSshuttle.emergency.getModeStr()
if(ETA)
stat(null, "[ETA] [SSshuttle.emergency.getTimerStr()]")
if(client && client.holder)
if(statpanel("MC"))
var/turf/T = get_turf(client.eye)
stat("Location:", COORD(T))
stat("CPU:", "[world.cpu]")
stat("Instances:", "[num2text(world.contents.len, 10)]")
stat("World Time:", "[world.time]")
GLOB.stat_entry()
config.stat_entry()
stat(null)
if(Master)
Master.stat_entry()
else
stat("Master Controller:", "ERROR")
if(Failsafe)
Failsafe.stat_entry()
else
stat("Failsafe Controller:", "ERROR")
if(Master)
stat(null)
for(var/datum/controller/subsystem/SS in Master.subsystems)
SS.stat_entry()
GLOB.cameranet.stat_entry()
if(statpanel("Tickets"))
GLOB.ahelp_tickets.stat_entry()
if(length(GLOB.sdql2_queries))
if(statpanel("SDQL2"))
stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj)
for(var/i in GLOB.sdql2_queries)
var/datum/sdql2_query/Q = i
Q.generate_stat()
if(listed_turf && client)
if(!TurfAdjacent(listed_turf))
listed_turf = null
else
statpanel(listed_turf.name, null, listed_turf)
var/list/overrides = list()
for(var/image/I in client.images)
if(I.loc && I.loc.loc == listed_turf && I.override)
overrides += I.loc
for(var/atom/A in listed_turf)
if(!A.mouse_opacity)
continue
if(A.invisibility > see_invisible)
continue
if(overrides.len && (A in overrides))
continue
if(A.IsObscured())
continue
statpanel(listed_turf.name, null, A)
if(mind)
add_spells_to_statpanel(mind.spell_list)
add_spells_to_statpanel(mob_spell_list)
/**
* Convert a list of spells into a displyable list for the statpanel
*
* Shows charge and other important info
*/
/mob/proc/add_spells_to_statpanel(list/spells)
for(var/obj/effect/proc_holder/spell/S in spells)
if(S.can_be_cast_by(src))
switch(S.charge_type)
if("recharge")
statpanel("[S.panel]","[S.charge_counter/10.0]/[S.charge_max/10]",S)
if("charges")
statpanel("[S.panel]","[S.charge_counter]/[S.charge_max]",S)
if("holdervar")
statpanel("[S.panel]","[S.holder_var_type] [S.holder_var_amount]",S)
#define MOB_FACE_DIRECTION_DELAY 1
// facing verbs
/**
* Returns true if a mob can turn to face things
*
* Conditions:
* * client.last_turn > world.time
* * not dead or unconcious
* * not anchored
* * no transform not set
* * we are not restrained
*/
/mob/proc/canface()
if(world.time < client.last_turn)
return FALSE
if(stat == DEAD || stat == UNCONSCIOUS)
return FALSE
if(anchored)
return FALSE
if(notransform)
return FALSE
if(restrained())
return FALSE
return TRUE
///Checks mobility move as well as parent checks
/mob/living/canface()
if(!(mobility_flags & MOBILITY_MOVE))
return FALSE
return ..()
/mob/dead/observer/canface()
return TRUE
///Hidden verb to turn east
/mob/verb/eastface()
set hidden = TRUE
if(!canface())
return FALSE
setDir(EAST)
client.last_turn = world.time + MOB_FACE_DIRECTION_DELAY
return TRUE
///Hidden verb to turn west
/mob/verb/westface()
set hidden = TRUE
if(!canface())
return FALSE
setDir(WEST)
client.last_turn = world.time + MOB_FACE_DIRECTION_DELAY
return TRUE
///Hidden verb to turn north
/mob/verb/northface()
set hidden = TRUE
if(!canface())
return FALSE
setDir(NORTH)
client.last_turn = world.time + MOB_FACE_DIRECTION_DELAY
return TRUE
///Hidden verb to turn south
/mob/verb/southface()
set hidden = TRUE
if(!canface())
return FALSE
setDir(SOUTH)
client.last_turn = world.time + MOB_FACE_DIRECTION_DELAY
return TRUE
///This might need a rename but it should replace the can this mob use things check
/mob/proc/IsAdvancedToolUser()
return FALSE
/mob/proc/swap_hand()
var/obj/item/held_item = get_active_held_item()
if(SEND_SIGNAL(src, COMSIG_MOB_SWAP_HANDS, held_item) & COMPONENT_BLOCK_SWAP)
to_chat(src, "<span class='warning'>Your other hand is too busy holding [held_item].</span>")
return FALSE
return TRUE
/mob/proc/activate_hand(selhand)
return
/mob/proc/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //For sec bot threat assessment
return 0
///Get the ghost of this mob (from the mind)
/mob/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients)
if(mind)
return mind.get_ghost(even_if_they_cant_reenter, ghosts_with_clients)
///Force get the ghost from the mind
/mob/proc/grab_ghost(force)
if(mind)
return mind.grab_ghost(force = force)
///Notify a ghost that it's body is being cloned
/mob/proc/notify_ghost_cloning(message = "Someone is trying to revive you. Re-enter your corpse if you want to be revived!", sound = 'sound/effects/genetics.ogg', atom/source = null, flashwindow)
var/mob/dead/observer/ghost = get_ghost()
if(ghost)
ghost.notify_cloning(message, sound, source, flashwindow)
return ghost
///Add a spell to the mobs spell list
/mob/proc/AddSpell(obj/effect/proc_holder/spell/S)
mob_spell_list += S
S.action.Grant(src)
///Remove a spell from the mobs spell list
/mob/proc/RemoveSpell(obj/effect/proc_holder/spell/spell)
if(!spell)
return
for(var/X in mob_spell_list)
var/obj/effect/proc_holder/spell/S = X
if(istype(S, spell))
mob_spell_list -= S
qdel(S)
///Return any anti magic atom on this mob that matches the magic type
/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE)
if(!magic && !holy && !tinfoil)
return
var/list/protection_sources = list()
if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, src, magic, holy, tinfoil, chargecost, self, protection_sources) & COMPONENT_BLOCK_MAGIC)
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
/**
* Buckle to another mob
*
* You can buckle on mobs if you're next to them since most are dense
*
* Turns you to face the other mob too
*/
/mob/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
if(M.buckled)
return FALSE
var/turf/T = get_turf(src)
if(M.loc != T)
var/old_density = density
density = FALSE
var/can_step = step_towards(M, T)
density = old_density
if(!can_step)
return FALSE
return ..()
///Call back post buckle to a mob to offset your visual height
/mob/post_buckle_mob(mob/living/M)
var/height = M.get_mob_buckling_height(src)
M.pixel_y = initial(M.pixel_y) + height
if(M.layer <= layer) //make sure they stay above our current layer
M.layer = layer + 0.1
///Call back post unbuckle from a mob, (reset your visual height here)
/mob/post_unbuckle_mob(mob/living/M)
M.layer = initial(M.layer)
M.pixel_y = initial(M.pixel_y)
///returns the height in pixel the mob should have when buckled to another mob.
/mob/proc/get_mob_buckling_height(mob/seat)
if(isliving(seat))
var/mob/living/L = seat
if(L.mob_size <= MOB_SIZE_SMALL) //being on top of a small mob doesn't put you very high.
return 0
return 9
///can the mob be buckled to something by default?
/mob/proc/can_buckle()
return 1
///can the mob be unbuckled from something by default?
/mob/proc/can_unbuckle()
return 1
///Can the mob interact() with an atom?
/mob/proc/can_interact_with(atom/A)
return IsAdminGhost(src) || Adjacent(A)
///Can the mob use Topic to interact with machines
/mob/proc/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE)
return
///Can this mob use storage
/mob/proc/canUseStorage()
return FALSE
/**
* Check if the other mob has any factions the same as us
*
* If exact match is set, then all our factions must match exactly
*/
/mob/proc/faction_check_mob(mob/target, exact_match)
if(exact_match) //if we need an exact match, we need to do some bullfuckery.
var/list/faction_src = faction.Copy()
var/list/faction_target = target.faction.Copy()
if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list.
faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match.
if(!("[REF(target)]" in faction_src))
faction_target -= "[REF(target)]" //same thing here.
return faction_check(faction_src, faction_target, TRUE)
return faction_check(faction, target.faction, FALSE)
/*
* Compare two lists of factions, returning true if any match
*
* If exact match is passed through we only return true if both faction lists match equally
*/
/proc/faction_check(list/faction_A, list/faction_B, exact_match)
var/list/match_list
if(exact_match)
match_list = faction_A&faction_B //only items in both lists
var/length = LAZYLEN(match_list)
if(length)
return (length == LAZYLEN(faction_A)) //if they're not the same len(gth) or we don't have a len, then this isn't an exact match.
else
match_list = faction_A&faction_B
return LAZYLEN(match_list)
return FALSE
/**
* Fully update the name of a mob
*
* This will update a mob's name, real_name, mind.name, GLOB.data_core records, pda, id and traitor text
*
* Calling this proc without an oldname will only update the mob and skip updating the pda, id and records ~Carn
*/
/mob/proc/fully_replace_character_name(oldname,newname)
log_message("[src] name changed from [oldname] to [newname]", LOG_OWNERSHIP)
if(!newname)
return 0
log_played_names(ckey,newname)
real_name = newname
name = newname
if(mind)
mind.name = newname
if(mind.key)
log_played_names(mind.key,newname) //Just in case the mind is unsynced at the moment.
if(oldname)
//update the datacore records! This is goig to be a bit costly.
replace_records_name(oldname,newname)
//update our pda and id if we have them on our person
replace_identification_name(oldname,newname)
for(var/datum/mind/T in SSticker.minds)
for(var/datum/objective/obj in T.get_all_objectives())
// Only update if this player is a target
if(obj.target && obj.target.current && obj.target.current.real_name == name)
obj.update_explanation_text()
return 1
///Updates GLOB.data_core records with new name , see mob/living/carbon/human
/mob/proc/replace_records_name(oldname,newname)
return
///update the ID name of this mob
/mob/proc/replace_identification_name(oldname,newname)
var/list/searching = GetAllContents()
var/search_id = 1
var/search_pda = 1
for(var/A in searching)
if( search_id && istype(A, /obj/item/card/id) )
var/obj/item/card/id/ID = A
if(ID.registered_name == oldname)
ID.registered_name = newname
ID.update_label()
if(ID.registered_account?.account_holder == oldname)
ID.registered_account.account_holder = newname
if(!search_pda)
break
search_id = 0
else if( search_pda && istype(A, /obj/item/pda) )
var/obj/item/pda/PDA = A
if(PDA.owner == oldname)
PDA.owner = newname
PDA.update_label()
if(!search_id)
break
search_pda = 0
/mob/proc/update_stat()
return
/mob/proc/update_health_hud()
return
///Update the lighting plane and sight of this mob (sends COMSIG_MOB_UPDATE_SIGHT)
/mob/proc/update_sight()
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
sync_lighting_plane_alpha()
///Set the lighting plane hud alpha to the mobs lighting_alpha var
/mob/proc/sync_lighting_plane_alpha()
if(hud_used)
var/obj/screen/plane_master/lighting/L = hud_used.plane_masters["[LIGHTING_PLANE]"]
if (L)
L.alpha = lighting_alpha
///Update the mouse pointer of the attached client in this mob
/mob/proc/update_mouse_pointer()
if (!client)
return
client.mouse_pointer_icon = initial(client.mouse_pointer_icon)
if (ismecha(loc))
var/obj/mecha/M = loc
if(M.mouse_pointer)
client.mouse_pointer_icon = M.mouse_pointer
else if (istype(loc, /obj/vehicle/sealed))
var/obj/vehicle/sealed/E = loc
if(E.mouse_pointer)
client.mouse_pointer_icon = E.mouse_pointer
///This mob is abile to read books
/mob/proc/is_literate()
return FALSE
///Can this mob read (is literate and not blind)
/mob/proc/can_read(obj/O)
if(is_blind())
to_chat(src, "<span class='warning'>As you are trying to read [O], you suddenly feel very stupid!</span>")
return
if(!is_literate())
to_chat(src, "<span class='notice'>You try to read [O], but can't comprehend any of it.</span>")
return
return TRUE
///Can this mob hold items
/mob/proc/can_hold_items()
return FALSE
///Get the id card on this mob
/mob/proc/get_idcard(hand_first)
return
/mob/proc/get_id_in_hand()
return
/**
* Get the mob VV dropdown extras
*/
/mob/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---------")
VV_DROPDOWN_OPTION(VV_HK_GIB, "Gib")
VV_DROPDOWN_OPTION(VV_HK_GIVE_SPELL, "Give Spell")
VV_DROPDOWN_OPTION(VV_HK_REMOVE_SPELL, "Remove Spell")
VV_DROPDOWN_OPTION(VV_HK_GIVE_DISEASE, "Give Disease")
VV_DROPDOWN_OPTION(VV_HK_GODMODE, "Toggle Godmode")
VV_DROPDOWN_OPTION(VV_HK_DROP_ALL, "Drop Everything")
VV_DROPDOWN_OPTION(VV_HK_REGEN_ICONS, "Regenerate Icons")
VV_DROPDOWN_OPTION(VV_HK_PLAYER_PANEL, "Show player panel")
VV_DROPDOWN_OPTION(VV_HK_BUILDMODE, "Toggle Buildmode")
VV_DROPDOWN_OPTION(VV_HK_DIRECT_CONTROL, "Assume Direct Control")
VV_DROPDOWN_OPTION(VV_HK_GIVE_DIRECT_CONTROL, "Give Direct Control")
VV_DROPDOWN_OPTION(VV_HK_OFFER_GHOSTS, "Offer Control to Ghosts")
/mob/vv_do_topic(list/href_list)
. = ..()
if(href_list[VV_HK_REGEN_ICONS])
if(!check_rights(NONE))
return
regenerate_icons()
if(href_list[VV_HK_PLAYER_PANEL])
if(!check_rights(NONE))
return
usr.client.holder.show_player_panel(src)
if(href_list[VV_HK_GODMODE])
if(!check_rights(R_ADMIN))
return
usr.client.cmd_admin_godmode(src)
if(href_list[VV_HK_GIVE_SPELL])
if(!check_rights(NONE))
return
usr.client.give_spell(src)
if(href_list[VV_HK_REMOVE_SPELL])
if(!check_rights(NONE))
return
usr.client.remove_spell(src)
if(href_list[VV_HK_GIVE_DISEASE])
if(!check_rights(NONE))
return
usr.client.give_disease(src)
if(href_list[VV_HK_GIB])
if(!check_rights(R_FUN))
return
usr.client.cmd_admin_gib(src)
if(href_list[VV_HK_BUILDMODE])
if(!check_rights(R_BUILD))
return
togglebuildmode(src)
if(href_list[VV_HK_DROP_ALL])
if(!check_rights(NONE))
return
usr.client.cmd_admin_drop_everything(src)
if(href_list[VV_HK_DIRECT_CONTROL])
if(!check_rights(NONE))
return
usr.client.cmd_assume_direct_control(src)
if(href_list[VV_HK_GIVE_DIRECT_CONTROL])
if(!check_rights(NONE))
return
usr.client.cmd_give_direct_control(src)
if(href_list[VV_HK_OFFER_GHOSTS])
if(!check_rights(NONE))
return
offer_control(src)
/**
* extra var handling for the logging var
*/
/mob/vv_get_var(var_name)
switch(var_name)
if("logging")
return debug_variable(var_name, logging, 0, src, FALSE)
. = ..()
/mob/vv_auto_rename(new_name)
//Do not do parent's actions, as we *usually* do this differently.
fully_replace_character_name(real_name, new_name)
///Show the language menu for this mob
/mob/verb/open_language_menu()
set name = "Open Language Menu"
set category = "IC"
var/datum/language_holder/H = get_language_holder()
H.open_language_menu(usr)
///Adjust the nutrition of a mob
/mob/proc/adjust_nutrition(var/change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks
nutrition = max(0, nutrition + change)
///Force set the mob nutrition
/mob/proc/set_nutrition(var/change) //Seriously fuck you oldcoders.
nutrition = max(0, change)
///Set the movement type of the mob and update it's movespeed
/mob/setMovetype(newval)
. = ..()
update_movespeed(FALSE)
/// Updates the grab state of the mob and updates movespeed
/mob/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)
/mob/proc/update_equipment_speed_mods()
var/speedies = equipped_speed_mods()
if(!speedies)
remove_movespeed_modifier(/datum/movespeed_modifier/equipment_speedmod)
else
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/equipment_speedmod, multiplicative_slowdown = speedies)
/// Gets the combined speed modification of all worn items
/// Except base mob type doesnt really wear items
/mob/proc/equipped_speed_mods()
for(var/obj/item/I in held_items)
if(I.item_flags & SLOWS_WHILE_IN_HAND)
. += I.slowdown
/mob/proc/set_stat(new_stat)
if(new_stat == stat)
return
SEND_SIGNAL(src, COMSIG_MOB_STATCHANGE, new_stat)
. = stat
stat = new_stat