mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* Steals tg's span macros * Fix alphabet * Updated some more spans * Misses a conflict * Fix compile errors * Converts more spans * oops
579 lines
17 KiB
Plaintext
579 lines
17 KiB
Plaintext
// see _DEFINES/is_helpers.dm for mob type checks
|
|
|
|
///Find the mob at the bottom of a buckle chain
|
|
/mob/proc/lowest_buckled_mob()
|
|
. = src
|
|
if(buckled && ismob(buckled))
|
|
var/mob/Buckled = buckled
|
|
. = Buckled.lowest_buckled_mob()
|
|
|
|
///Convert a PRECISE ZONE into the BODY_ZONE
|
|
/proc/check_zone(zone)
|
|
if(!zone)
|
|
return BODY_ZONE_CHEST
|
|
switch(zone)
|
|
if(BODY_ZONE_PRECISE_EYES)
|
|
zone = BODY_ZONE_HEAD
|
|
if(BODY_ZONE_PRECISE_MOUTH)
|
|
zone = BODY_ZONE_HEAD
|
|
if(BODY_ZONE_PRECISE_L_HAND)
|
|
zone = BODY_ZONE_L_ARM
|
|
if(BODY_ZONE_PRECISE_R_HAND)
|
|
zone = BODY_ZONE_R_ARM
|
|
if(BODY_ZONE_PRECISE_L_FOOT)
|
|
zone = BODY_ZONE_L_LEG
|
|
if(BODY_ZONE_PRECISE_R_FOOT)
|
|
zone = BODY_ZONE_R_LEG
|
|
if(BODY_ZONE_PRECISE_GROIN)
|
|
zone = BODY_ZONE_CHEST
|
|
return zone
|
|
|
|
/**
|
|
* Return the zone or randomly, another valid zone
|
|
*
|
|
* probability controls the chance it chooses the passed in zone, or another random zone
|
|
* defaults to 80
|
|
*/
|
|
/proc/ran_zone(zone, probability = 80)
|
|
if(prob(probability))
|
|
zone = check_zone(zone)
|
|
else
|
|
zone = pickweight(list(BODY_ZONE_HEAD = 1, BODY_ZONE_CHEST = 1, BODY_ZONE_L_ARM = 4, BODY_ZONE_R_ARM = 4, BODY_ZONE_L_LEG = 4, BODY_ZONE_R_LEG = 4))
|
|
return zone
|
|
|
|
///Would this zone be above the neck
|
|
/proc/above_neck(zone)
|
|
var/list/zones = list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES)
|
|
if(zones.Find(zone))
|
|
return 1
|
|
else
|
|
return 0
|
|
/**
|
|
* Convert random parts of a passed in message to stars
|
|
*
|
|
* * n - the string to convert
|
|
* * pr - probability any character gets changed
|
|
*
|
|
* This proc is dangerously laggy, avoid it or die
|
|
*/
|
|
/proc/stars(n, pr)
|
|
n = html_encode(n)
|
|
if (pr == null)
|
|
pr = 25
|
|
if (pr <= 0)
|
|
return null
|
|
else
|
|
if (pr >= 100)
|
|
return n
|
|
var/te = n
|
|
var/t = ""
|
|
n = length(n)
|
|
|
|
for(var/p = 1 to min(n,MAX_BROADCAST_LEN))
|
|
if ((copytext(te, p, p + 1) == " " || prob(pr)))
|
|
t = text("[][]", t, copytext(te, p, p + 1))
|
|
else
|
|
t = text("[]*", t)
|
|
if(n > MAX_BROADCAST_LEN)
|
|
t += "..." //signals missing text
|
|
return sanitize(t)
|
|
/**
|
|
* Makes you speak like you're drunk
|
|
*/
|
|
/proc/slur(phrase)
|
|
phrase = html_decode(phrase)
|
|
var/leng = length(phrase)
|
|
. = ""
|
|
var/newletter = ""
|
|
var/rawchar = ""
|
|
for(var/i = 1, i <= leng, i += length(rawchar))
|
|
rawchar = newletter = phrase[i]
|
|
if(rand(1, 3) == 3)
|
|
var/lowerletter = lowertext(newletter)
|
|
if(lowerletter == "o")
|
|
newletter = "u"
|
|
else if(lowerletter == "s")
|
|
newletter = "ch"
|
|
else if(lowerletter == "a")
|
|
newletter = "ah"
|
|
else if(lowerletter == "u")
|
|
newletter = "oo"
|
|
else if(lowerletter == "c")
|
|
newletter = "k"
|
|
if(rand(1, 20) == 20)
|
|
if(newletter == " ")
|
|
newletter = "...huuuhhh..."
|
|
else if(newletter == ".")
|
|
newletter = " *BURP*."
|
|
switch(rand(1, 20))
|
|
if(1)
|
|
newletter += "'"
|
|
if(10)
|
|
newletter += "[newletter]"
|
|
if(20)
|
|
newletter += "[newletter][newletter]"
|
|
. += "[newletter]"
|
|
return sanitize(.)
|
|
|
|
/// Makes you talk like you got cult stunned, which is slurring but with some dark messages
|
|
/proc/cultslur(phrase) // Inflicted on victims of a stun talisman
|
|
phrase = html_decode(phrase)
|
|
var/leng = length(phrase)
|
|
. = ""
|
|
var/newletter = ""
|
|
var/rawchar = ""
|
|
for(var/i = 1, i <= leng, i += length(rawchar))
|
|
rawchar = newletter = phrase[i]
|
|
if(rand(1, 2) == 2)
|
|
var/lowerletter = lowertext(newletter)
|
|
if(lowerletter == "o")
|
|
newletter = "u"
|
|
else if(lowerletter == "t")
|
|
newletter = "ch"
|
|
else if(lowerletter == "a")
|
|
newletter = "ah"
|
|
else if(lowerletter == "u")
|
|
newletter = "oo"
|
|
else if(lowerletter == "c")
|
|
newletter = " NAR "
|
|
else if(lowerletter == "s")
|
|
newletter = " SIE "
|
|
if(rand(1, 4) == 4)
|
|
if(newletter == " ")
|
|
newletter = " no hope... "
|
|
else if(newletter == "H")
|
|
newletter = " IT COMES... "
|
|
|
|
switch(rand(1, 15))
|
|
if(1)
|
|
newletter = "'"
|
|
if(2)
|
|
newletter += "agn"
|
|
if(3)
|
|
newletter = "fth"
|
|
if(4)
|
|
newletter = "nglu"
|
|
if(5)
|
|
newletter = "glor"
|
|
. += newletter
|
|
return sanitize(.)
|
|
|
|
///Adds stuttering to the message passed in
|
|
/proc/stutter(phrase)
|
|
phrase = html_decode(phrase)
|
|
var/leng = length(phrase)
|
|
. = ""
|
|
var/newletter = ""
|
|
var/rawchar
|
|
for(var/i = 1, i <= leng, i += length(rawchar))
|
|
rawchar = newletter = phrase[i]
|
|
if(prob(80) && !(lowertext(newletter) in list("a", "e", "i", "o", "u", " ")))
|
|
if(prob(10))
|
|
newletter = "[newletter]-[newletter]-[newletter]-[newletter]"
|
|
else if(prob(20))
|
|
newletter = "[newletter]-[newletter]-[newletter]"
|
|
else if (prob(5))
|
|
newletter = ""
|
|
else
|
|
newletter = "[newletter]-[newletter]"
|
|
. += newletter
|
|
return sanitize(.)
|
|
|
|
///Convert a message to derpy speak
|
|
/proc/derpspeech(message, stuttering)
|
|
message = replacetext(message, " am ", " ")
|
|
message = replacetext(message, " is ", " ")
|
|
message = replacetext(message, " are ", " ")
|
|
message = replacetext(message, "you", "u")
|
|
message = replacetext(message, "help", "halp")
|
|
message = replacetext(message, "grief", "grife")
|
|
message = replacetext(message, "space", "spess")
|
|
message = replacetext(message, "carp", "crap")
|
|
message = replacetext(message, "reason", "raisin")
|
|
if(prob(50))
|
|
message = uppertext(message)
|
|
message += "[stutter(pick("!", "!!", "!!!"))]"
|
|
if(!stuttering && prob(15))
|
|
message = stutter(message)
|
|
return message
|
|
|
|
/**
|
|
* Turn text into complete gibberish!
|
|
*
|
|
* text is the inputted message, and any value higher than 70 for chance will cause letters to be replaced instead of added
|
|
*/
|
|
/proc/Gibberish(text, replace_characters = FALSE, chance = 50)
|
|
text = html_decode(text)
|
|
. = ""
|
|
var/rawchar = ""
|
|
var/letter = ""
|
|
var/lentext = length(text)
|
|
for(var/i = 1, i <= lentext, i += length(rawchar))
|
|
rawchar = letter = text[i]
|
|
if(prob(chance))
|
|
if(replace_characters)
|
|
letter = ""
|
|
for(var/j in 1 to rand(0, 2))
|
|
letter += pick("#", "@", "*", "&", "%", "$", "/", "<", ">", ";", "*", "*", "*", "*", "*", "*", "*")
|
|
. += letter
|
|
return sanitize(.)
|
|
|
|
|
|
|
|
|
|
///Shake the camera of the person viewing the mob SO REAL!
|
|
/proc/shake_camera(mob/M, duration, strength=1)
|
|
if(!M || !M.client || duration < 1)
|
|
return
|
|
var/client/C = M.client
|
|
var/oldx = C.pixel_x
|
|
var/oldy = C.pixel_y
|
|
var/max = strength*world.icon_size
|
|
var/min = -(strength*world.icon_size)
|
|
|
|
for(var/i in 0 to duration-1)
|
|
if (i == 0)
|
|
animate(C, pixel_x=rand(min,max), pixel_y=rand(min,max), time=1)
|
|
else
|
|
animate(pixel_x=rand(min,max), pixel_y=rand(min,max), time=1)
|
|
animate(pixel_x=oldx, pixel_y=oldy, time=1)
|
|
|
|
|
|
///Find if the message has the real name of any user mob in the mob_list
|
|
/proc/findname(msg)
|
|
if(!istext(msg))
|
|
msg = "[msg]"
|
|
for(var/i in GLOB.mob_list)
|
|
var/mob/M = i
|
|
if(M.real_name == msg)
|
|
return M
|
|
return 0
|
|
|
|
///Find the first name of a mob from the real name with regex
|
|
/mob/proc/first_name()
|
|
var/static/regex/firstname = new("^\[^\\s-\]+") //First word before whitespace or "-"
|
|
firstname.Find(real_name)
|
|
return firstname.match
|
|
|
|
|
|
/**
|
|
* change a mob's act-intent.
|
|
*
|
|
* Input the intent as a string such as "help" or use "right"/"left
|
|
*/
|
|
/mob/verb/a_intent_change(input as text)
|
|
set name = "a-intent"
|
|
set hidden = TRUE
|
|
|
|
if(!possible_a_intents || !possible_a_intents.len)
|
|
return
|
|
|
|
if(input in possible_a_intents)
|
|
a_intent = input
|
|
else
|
|
var/current_intent = possible_a_intents.Find(a_intent)
|
|
|
|
if(!current_intent)
|
|
// Failsafe. Just in case some badmin was playing with VV.
|
|
current_intent = 1
|
|
|
|
if(input == INTENT_HOTKEY_RIGHT)
|
|
current_intent += 1
|
|
if(input == INTENT_HOTKEY_LEFT)
|
|
current_intent -= 1
|
|
|
|
// Handle looping
|
|
if(current_intent < 1)
|
|
current_intent = possible_a_intents.len
|
|
if(current_intent > possible_a_intents.len)
|
|
current_intent = 1
|
|
|
|
a_intent = possible_a_intents[current_intent]
|
|
|
|
if(hud_used && hud_used.action_intent)
|
|
hud_used.action_intent.icon_state = "[a_intent]"
|
|
|
|
///Checks if passed through item is blind
|
|
/proc/is_blind(A)
|
|
SHOULD_BE_PURE(TRUE)
|
|
if(ismob(A))
|
|
var/mob/B = A
|
|
return B.eye_blind
|
|
return FALSE
|
|
|
|
///Is the mob hallucinating?
|
|
/mob/proc/hallucinating()
|
|
return FALSE
|
|
|
|
|
|
// moved out of admins.dm because things other than admin procs were calling this.
|
|
/**
|
|
* Is this mob special to the gamemode?
|
|
*
|
|
* returns 1 for special characters and 2 for heroes of gamemode
|
|
*
|
|
*/
|
|
/proc/is_special_character(mob/M)
|
|
if(!SSticker.HasRoundStarted())
|
|
return FALSE
|
|
if(!istype(M))
|
|
return FALSE
|
|
if(issilicon(M))
|
|
if(iscyborg(M)) //For cyborgs, returns 1 if the cyborg has a law 0 and special_role. Returns 0 if the borg is merely slaved to an AI traitor.
|
|
return FALSE
|
|
else if(isAI(M))
|
|
var/mob/living/silicon/ai/A = M
|
|
if(A.laws && A.laws.zeroth && A.mind && A.mind.special_role)
|
|
return TRUE
|
|
return FALSE
|
|
if(M.mind?.special_role)//If they have a mind and special role, they are some type of traitor or antagonist.
|
|
switch(SSticker.mode.config_tag)
|
|
if("revolution")
|
|
if(is_revolutionary(M))
|
|
return 2
|
|
if("cult")
|
|
if(M.mind in SSticker.mode.cult)
|
|
return 2
|
|
if("nuclear")
|
|
if(M.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE))
|
|
return 2
|
|
if("changeling")
|
|
if(M.mind.has_antag_datum(/datum/antagonist/changeling,TRUE))
|
|
return 2
|
|
if("wizard")
|
|
if(iswizard(M))
|
|
return 2
|
|
if("apprentice")
|
|
if(M.mind in SSticker.mode.apprentices)
|
|
return 2
|
|
if("monkey")
|
|
if(isliving(M))
|
|
var/mob/living/L = M
|
|
if(L.diseases && (locate(/datum/disease/transformation/jungle_fever) in L.diseases))
|
|
return 2
|
|
return TRUE
|
|
if(M.mind && LAZYLEN(M.mind.antag_datums)) //they have an antag datum!
|
|
return TRUE
|
|
return FALSE
|
|
|
|
|
|
/mob/proc/reagent_check(datum/reagent/R) // utilized in the species code
|
|
return 1
|
|
|
|
|
|
/**
|
|
* Fancy notifications for ghosts
|
|
*
|
|
* The kitchen sink of notification procs
|
|
*
|
|
* Arguments:
|
|
* * message
|
|
* * ghost_sound sound to play
|
|
* * enter_link Href link to enter the ghost role being notified for
|
|
* * source The source of the notification
|
|
* * alert_overlay The alert overlay to show in the alert message
|
|
* * action What action to take upon the ghost interacting with the notification, defaults to NOTIFY_JUMP
|
|
* * flashwindow Flash the byond client window
|
|
* * ignore_key Ignore keys if they're in the GLOB.poll_ignore list
|
|
* * header The header of the notifiaction
|
|
* * notify_suiciders If it should notify suiciders (who do not qualify for many ghost roles)
|
|
* * notify_volume How loud the sound should be to spook the user
|
|
*/
|
|
/proc/notify_ghosts(var/message, var/ghost_sound = null, var/enter_link = null, var/atom/source = null, var/mutable_appearance/alert_overlay = null, var/action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, header = null, notify_suiciders = TRUE, var/notify_volume = 100) //Easy notification of ghosts.
|
|
if(ignore_mapload && SSatoms.initialized != INITIALIZATION_INNEW_REGULAR) //don't notify for objects created during a map load
|
|
return
|
|
for(var/mob/dead/observer/O in GLOB.player_list)
|
|
if(O.client)
|
|
if(!notify_suiciders && (O in GLOB.suicided_mob_list))
|
|
continue
|
|
if (ignore_key && (O.ckey in GLOB.poll_ignore[ignore_key]))
|
|
continue
|
|
var/orbit_link
|
|
if (source && (action == NOTIFY_ORBIT || action == NOTIFY_ATTACKORBIT))
|
|
orbit_link = " <a href='?src=[REF(O)];follow=[REF(source)]'>(Orbit)</a>"
|
|
to_chat(O, span_ghostalert("[message][(enter_link) ? " [enter_link]" : ""][orbit_link]"))
|
|
if(ghost_sound)
|
|
SEND_SOUND(O, sound(ghost_sound, volume = notify_volume))
|
|
if(flashwindow)
|
|
window_flash(O.client)
|
|
if(source)
|
|
var/obj/screen/alert/notify_action/A = O.throw_alert("[REF(source)]_notify_action", /obj/screen/alert/notify_action)
|
|
if(A)
|
|
if(O.client.prefs && O.client.prefs.UI_style)
|
|
A.icon = ui_style2icon(O.client.prefs.UI_style)
|
|
if (header)
|
|
A.name = header
|
|
A.desc = message
|
|
A.action = action
|
|
A.target = source
|
|
if(!alert_overlay)
|
|
alert_overlay = new(source)
|
|
alert_overlay.layer = FLOAT_LAYER
|
|
alert_overlay.plane = FLOAT_PLANE
|
|
A.add_overlay(alert_overlay)
|
|
|
|
/**
|
|
* Heal a robotic body part on a mob
|
|
*/
|
|
/proc/item_heal_robotic(mob/living/carbon/human/H, mob/user, brute_heal, burn_heal)
|
|
var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected))
|
|
if(affecting && affecting.status == BODYPART_ROBOTIC)
|
|
var/dam //changes repair text based on how much brute/burn was supplied
|
|
if(brute_heal > burn_heal)
|
|
dam = 1
|
|
else
|
|
dam = 0
|
|
if((brute_heal > 0 && affecting.brute_dam > 0) || (burn_heal > 0 && affecting.burn_dam > 0))
|
|
if(affecting.heal_damage(brute_heal, burn_heal, 0, BODYPART_ROBOTIC))
|
|
H.update_damage_overlays()
|
|
user.visible_message("[user] has fixed some of the [dam ? "dents on" : "burnt wires in"] [H]'s [affecting.name].", \
|
|
span_notice("You fix some of the [dam ? "dents on" : "burnt wires in"] [H == user ? "your" : "[H]'s"] [affecting.name]."))
|
|
return 1 //successful heal
|
|
else
|
|
to_chat(user, span_warning("[affecting] is already in good condition!"))
|
|
|
|
///Is the passed in mob an admin ghost
|
|
/proc/IsAdminGhost(var/mob/user)
|
|
if(!user) //Are they a mob? Auto interface updates call this with a null src
|
|
return
|
|
if(!user.client) // Do they have a client?
|
|
return
|
|
if(!isobserver(user)) // Are they a ghost?
|
|
return
|
|
if(!check_rights_for(user.client, R_ADMIN)) // Are they allowed?
|
|
return
|
|
if(!user.client.AI_Interact) // Do they have it enabled?
|
|
return
|
|
return TRUE
|
|
|
|
/**
|
|
* Offer control of the passed in mob to dead player
|
|
*
|
|
* Automatic logging and uses pollCandidatesForMob, how convenient
|
|
*/
|
|
/proc/offer_control(mob/M)
|
|
to_chat(M, "Control of your mob has been offered to dead players.")
|
|
if(usr)
|
|
log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.")
|
|
message_admins("[key_name_admin(usr)] has offered control of ([ADMIN_LOOKUPFLW(M)]) to ghosts")
|
|
var/poll_message = "Do you want to play as [M.real_name]?"
|
|
if(M?.mind?.assigned_role)
|
|
poll_message = "[poll_message] Job:[M.mind.assigned_role]."
|
|
if(M?.mind?.special_role)
|
|
poll_message = "[poll_message] Status:[M.mind.special_role]."
|
|
else if(M.mind)
|
|
var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/)
|
|
if(A)
|
|
poll_message = "[poll_message] Status:[A.name]."
|
|
var/list/mob/dead/observer/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 150, M)
|
|
|
|
if(LAZYLEN(candidates))
|
|
var/mob/dead/observer/C = pick(candidates)
|
|
to_chat(M, "Your mob has been taken over by a ghost!")
|
|
message_admins("[key_name_admin(C)] has taken control of ([ADMIN_LOOKUPFLW(M)])")
|
|
M.ghostize(0)
|
|
M.key = C.key
|
|
return TRUE
|
|
else
|
|
to_chat(M, "There were no ghosts willing to take control.")
|
|
message_admins("No ghosts were willing to take control of [ADMIN_LOOKUPFLW(M)])")
|
|
return FALSE
|
|
|
|
///Is the mob a flying mob
|
|
/mob/proc/is_flying(mob/M = src)
|
|
if(M.movement_type & FLYING)
|
|
return 1
|
|
else
|
|
return 0
|
|
|
|
/mob/proc/is_overdosed(mob/M = src)
|
|
for(var/datum/reagent/R as anything in M.reagents.reagent_list)
|
|
if(R.overdosed)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
///Clicks a random nearby mob with the source from this mob
|
|
/mob/proc/click_random_mob()
|
|
var/list/nearby_mobs = list()
|
|
for(var/mob/living/L in range(1, src))
|
|
if(L!=src)
|
|
nearby_mobs |= L
|
|
if(nearby_mobs.len)
|
|
var/mob/living/T = pick(nearby_mobs)
|
|
ClickOn(T)
|
|
|
|
/// Logs a message in a mob's individual log, and in the global logs as well if log_globally is true
|
|
/mob/log_message(message, message_type, color=null, log_globally = TRUE)
|
|
if(!LAZYLEN(message))
|
|
stack_trace("Empty message")
|
|
return
|
|
|
|
// Cannot use the list as a map if the key is a number, so we stringify it (thank you BYOND)
|
|
var/smessage_type = num2text(message_type)
|
|
|
|
if(client)
|
|
if(!islist(client.player_details.logging[smessage_type]))
|
|
client.player_details.logging[smessage_type] = list()
|
|
|
|
if(!islist(logging[smessage_type]))
|
|
logging[smessage_type] = list()
|
|
|
|
var/colored_message = message
|
|
if(color)
|
|
if(color[1] == "#")
|
|
colored_message = "<font color=[color]>[message]</font>"
|
|
else
|
|
colored_message = "<font color='[color]'>[message]</font>"
|
|
|
|
//This makes readability a bit better for admins.
|
|
switch(message_type)
|
|
if(LOG_WHISPER)
|
|
colored_message = "(WHISPER) [colored_message]"
|
|
if(LOG_OOC)
|
|
colored_message = "(OOC) [colored_message]"
|
|
if(LOG_ASAY)
|
|
colored_message = "(ASAY) [colored_message]"
|
|
if(LOG_EMOTE)
|
|
colored_message = "(EMOTE) [colored_message]"
|
|
|
|
var/list/timestamped_message = list("\[[time_stamp()]\] [key_name(src)] [loc_name(src)] (Event #[LAZYLEN(logging[smessage_type])])" = colored_message)
|
|
|
|
logging[smessage_type] += timestamped_message
|
|
|
|
if(client)
|
|
client.player_details.logging[smessage_type] += timestamped_message
|
|
|
|
..()
|
|
|
|
///Can the mob hear
|
|
/mob/proc/can_hear()
|
|
. = TRUE
|
|
|
|
/**
|
|
* Examine text for traits shared by multiple types.
|
|
*
|
|
* I wish examine was less copypasted. (oranges say, be the change you want to see buddy)
|
|
*/
|
|
/mob/proc/common_trait_examine()
|
|
if(HAS_TRAIT(src, TRAIT_DISSECTED))
|
|
. += "[span_notice("This body has been dissected and analyzed. It is no longer worth experimenting on.")]<br>"
|
|
|
|
/**
|
|
* Get the list of keywords for policy config
|
|
*
|
|
* This gets the type, mind assigned roles and antag datums as a list, these are later used
|
|
* to send the user relevant headadmin policy config
|
|
*/
|
|
/mob/proc/get_policy_keywords()
|
|
. = list()
|
|
. += "[type]"
|
|
if(mind)
|
|
. += mind.assigned_role
|
|
. += mind.special_role //In case there's something special leftover, try to avoid
|
|
for(var/datum/antagonist/A in mind.antag_datums)
|
|
. += "[A.type]"
|
|
|
|
///Can the mob see reagents inside of containers?
|
|
/mob/proc/can_see_reagents()
|
|
return stat == DEAD || has_unlimited_silicon_privilege //Dead guys and silicons can always see reagents
|