// 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 TRUE else return FALSE /** * Convert random parts of a passed in message to stars * * * phrase - the string to convert * * probability - probability any character gets changed * * This proc is dangerously laggy, avoid it or die */ /proc/stars(phrase, probability = 25) if(probability <= 0) return phrase phrase = html_decode(phrase) var/leng = length(phrase) . = "" var/char = "" for(var/i = 1, i <= leng, i += length(char)) char = phrase[i] if(char == " " || !prob(probability)) . += char else . += "*" return sanitize(.) /** * 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 = "" var/static/regex/nostutter = regex(@@[aeiouAEIOU ""''()[\]{}.!?,:;_`~-]@) for(var/i = 1, i <= leng, i += length(rawchar)) rawchar = newletter = phrase[i] if(prob(80) && !nostutter.Find(rawchar)) 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, replace_characters will cause original letters to be replaced and chance are the odds that a character gets modified. */ /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(.) #define TILES_PER_SECOND 0.7 ///Shake the camera of the person viewing the mob SO REAL! ///Takes the mob to shake, the time span to shake for, and the amount of tiles we're allowed to shake by in tiles ///Duration isn't taken as a strict limit, since we don't trust our coders to not make things feel shitty. So it's more like a soft cap. /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) //How much time to allot for each pixel moved var/time_scalar = (1 / world.icon_size) * TILES_PER_SECOND var/last_x = oldx var/last_y = oldy var/time_spent = 0 while(time_spent < duration) //Get a random pos in our box var/x_pos = rand(min, max) + oldx var/y_pos = rand(min, max) + oldy //We take the smaller of our two distances so things still have the propencity to feel somewhat jerky var/time = round(max(min(abs(last_x - x_pos), abs(last_y - y_pos)) * time_scalar, 1)) if (time_spent == 0) animate(C, pixel_x=x_pos, pixel_y=y_pos, time=time) else animate(pixel_x=x_pos, pixel_y=y_pos, time=time) last_x = x_pos last_y = y_pos //We go based on time spent, so there is a chance we'll overshoot our duration. Don't care time_spent += time animate(pixel_x=oldx, pixel_y=oldy, time=3) #undef TILES_PER_SECOND ///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 /// Find the last name of a mob from the real name with regex /mob/proc/last_name() var/static/regex/lasttname = new("\[^\\s-\]+$") //First word before whitespace or "-" lasttname.Find(real_name) return lasttname.match ///Returns a mob's real name between brackets. Useful when you want to display a mob's name alongside their real name /mob/proc/get_realname_string() if(real_name && real_name != name) return " \[[real_name]\]" return "" ///Checks if the mob is able to see or not. eye_blind is temporary blindness, the trait is if they're permanently blind. /mob/proc/is_blind() SHOULD_BE_PURE(TRUE) return eye_blind ? TRUE : HAS_TRAIT(src, TRAIT_BLIND) ///Is the mob hallucinating? /mob/proc/hallucinating() return FALSE // moved out of admins.dm because things other than admin procs were calling this. /// Returns TRUE if the game has started and we're either an AI with a 0th law, or we're someone with a special role/antag datum /proc/is_special_character(mob/M) if(!SSticker.HasRoundStarted()) return FALSE if(!istype(M)) return FALSE if(iscyborg(M)) //as a borg you're now beholden to your laws rather than greentext return FALSE if(isAI(M)) var/mob/living/silicon/ai/A = M return (A.laws?.zeroth && (A.mind?.special_role || !isnull(M.mind?.antag_datums))) if(M.mind?.special_role || !isnull(M.mind?.antag_datums)) //they have an antag datum! return TRUE return FALSE /mob/proc/reagent_check(datum/reagent/R, delta_time, times_fired) // utilized in the species code return TRUE /** * 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(message, ghost_sound = null, enter_link = null, atom/source = null, mutable_appearance/alert_overlay = null, action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, header = null, notify_suiciders = TRUE, 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(!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) orbit_link = " (Orbit)" 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/atom/movable/screen/alert/notify_action/A = O.throw_alert("[REF(source)]_notify_action", /atom/movable/screen/alert/notify_action) if(A) var/ui_style = O.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) var/erp_ui_style = O.client?.prefs?.read_preference(/datum/preference/choiced/ui_style) //SKYRAT EDIT - ADDITION - ERP ICONS FIX if(ui_style) A.icon = ui_style2icon(ui_style) A.icon = erp_ui_style2icon(erp_ui_style) //SKYRAT EDIT - ADDITION - ERP ICONS FIX 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(span_notice("[user] fixes 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 a ghost with admin powers, doesn't check for AI interact like isAdminGhost() used to /proc/isAdminObserver(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 return TRUE ///Is the passed in mob an admin ghost WITH AI INTERACT enabled /proc/isAdminGhostAI(mob/user) if(!isAdminObserver(user)) 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 poll_candidates_for_mob, 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) poll_message = "[poll_message] Job: [M.mind.assigned_role.title]." if(M.mind.special_role) poll_message = "[poll_message] Status: [M.mind.special_role]." else 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 = poll_candidates_for_mob(poll_message, ROLE_PAI, FALSE, 10 SECONDS, 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 M.client?.init_verbs() 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 ///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, MAX_BITFLAG_DIGITS) 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 = "[message]" else colored_message = "[message]" //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]" //SKYRAT EDIT ADDITION BEGIN if(LOG_SUBTLER) colored_message = "(EMOTE) (SUBTLER) [colored_message]" //SKYRAT EDIT ADDITION END 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)) var/dissectionmsg = "" if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Extraterrestrial Dissection")) dissectionmsg = " via Extraterrestrial Dissection. It is no longer worth experimenting on" else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Experimental Dissection")) dissectionmsg = " via Experimental Dissection" else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Thorough Dissection")) dissectionmsg = " via Thorough Dissection" . += "[span_notice("This body has been dissected and analyzed[dissectionmsg].")]
" if(HAS_TRAIT(src,TRAIT_HUSK)) . += span_warning("This body has been reduced to a grotesque husk.") /** * 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) if(mind.assigned_role.policy_index) . += mind.assigned_role.policy_index . += mind.special_role //In case there's something special leftover, try to avoid for(var/datum/antagonist/antag_datum as anything in mind.antag_datums) . += "[antag_datum.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 ///Can this mob hold items /mob/proc/can_hold_items(obj/item/I) return length(held_items)