/** * 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,"... You can almost hear someone talking ...", 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("\The [src] 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, "
[result.Join("\n")]
") #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 += "
[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, "

OOC Warning:

") to_chat(src, "Your flavor text is likely out of date! Change") /mob/proc/print_flavor_text() if (flavor_text && flavor_text != "") var/msg = replacetext(flavor_text, "\n", " ") if(length(msg) <= 40) return "[msg]" else return "[copytext_preserve_html(msg, 1, 37)]... More..." /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, "You must be observing to use this!") 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("[name][replacetext(flavor_text, "\n", "
")]
", "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, "You attempt to get a good grip on [selection] in your body.") // else // to_chat(U, "You attempt to get a good grip on [selection] in [S]'s body.") // if(!do_after(U, 30)) // return // if(!selection || !S || !U) // return // if(self) // visible_message("[src] rips [selection] out of their body.","You rip [selection] out of your body.") // else // visible_message("[usr] rips [selection] out of [src]'s body.","[usr] rips [selection] out of your body.") // 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, "[n] must be 2 or above, otherwise why are you rolling?") return to_chat(src, "Diceroll result: [rand(1, n)]") /** * 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)