/atom layer = TURF_LAYER plane = GAME_PLANE var/level = 2 var/flags = NONE var/flags_2 = NONE /// how the atom should handle ricochet behavior var/flags_ricochet = NONE var/list/fingerprints var/list/fingerprintshidden var/fingerprintslast = null ///For handling persistent filters var/list/filter_data /// pass_flags that we are. If any of this matches a pass_flag on a moving thing, by default, we let them through. var/pass_flags_self = NONE /// How this atom should react to having its astar blocking checked var/can_pathfind_pass = CANPATHFINDPASS_DENSITY var/list/blood_DNA var/blood_color /// Will the atom spread blood when touched? var/should_spread_blood = FALSE var/pass_flags = 0 /// The higher the germ level, the more germ on the atom. var/germ_level = GERM_LEVEL_AMBIENT /// Filter for actions - used by lighting overlays var/simulated = TRUE var/atom_say_verb = "says" /// What icon the mob uses for speechbubbles var/bubble_icon = "default" // Chemistry. var/container_type = NONE var/datum/reagents/reagents = null /// This atom's HUD (med/sec, etc) images. Associative list. var/list/image/hud_list /// HUD images that this atom can provide. var/list/hud_possible /// Value used to increment ex_act() if reactionary_explosions is on var/explosion_block = 0 // Detective Work, used for the duplicate data points kept in the scanners var/list/original_atom /// List of fibers that this atom has var/list/suit_fibers var/admin_spawned = FALSE //was this spawned by an admin? used for stat tracking stuff. var/initialized = FALSE var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. var/list/remove_overlays // a very temporary list of overlays to remove var/list/add_overlays // a very temporary list of overlays to add ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc. Single items are stored on their own, not in a list. var/list/managed_overlays var/list/atom_colours //used to store the different colors on an atom //its inherent color, the colored paint applied on it, special color effect etc... /// Radiation insulation types var/rad_insulation = RAD_NO_INSULATION /// Last name used to calculate a color for the chatmessage overlays. Used for caching. var/chat_color_name /// Last color calculated for the the chatmessage overlays. Used for caching. var/chat_color /* Smoothing Vars */ ///Icon-smoothing behavior. var/smoothing_flags = NONE ///Smoothing variable var/top_left_corner ///Smoothing variable var/top_right_corner ///Smoothing variable var/bottom_left_corner ///Smoothing variable var/bottom_right_corner ///What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it. var/list/smoothing_groups = null ///List of smoothing groups this atom can smooth with. If this is null and atom is smooth, it smooths only with itself. var/list/canSmoothWith = null ///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags. var/smoothing_junction = null //This starts as null for us to know when it's first set, but after that it will hold a 8-bit mask ranging from 0 to 255. ///Used for changing icon states for different base sprites. var/base_icon_state /// This var isn't actually used for anything, but is present so that DM's map reader doesn't forfeit on reading a JSON-serialized map. AKA DO NOT FUCK WITH var/map_json_data /* Lighting Vars */ /// Intensity of the light. Can be negative to remove light var/light_power = 1 // Range in tiles of the light. var/light_range = 0 // Hexadecimal RGB string representing the colour of the light. ALWAYS REMEMBER TO MAKE SURE THIS CAN'T BE NULL/NEGATIVE var/light_color // Our light source. Don't fuck with this directly unless you have a good reason! var/tmp/datum/light_source/light // Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list. var/tmp/list/light_sources // Variables for bloom and exposure var/glow_icon = 'icons/obj/lamps.dmi' var/exposure_icon = 'icons/effects/exposures.dmi' var/glow_icon_state var/glow_colored = TRUE var/exposure_icon_state var/exposure_colored = TRUE var/image/glow_overlay var/image/exposure_overlay /// The alternate appearances we own. Lazylist var/list/alternate_appearances /// The alternate appearances we're viewing, stored here to reestablish them after Logout()s. Lazylist var/list/viewing_alternate_appearances /// Contains the world.time of when we start dragging something with our mouse. Used to prevent weird situations where you fail to click on something var/drag_start = 0 ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this var/receive_ricochet_chance_mod = 1 ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom var/receive_ricochet_damage_coeff = 0.33 /atom/New(loc, ...) SHOULD_CALL_PARENT(TRUE) if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() GLOB._preloader.load(src) . = ..() attempt_init(arglist(args)) // This is distinct from /tg/ because of our space management system // This is overriden in /atom/movable and the parent isn't called if the SMS wants to deal with it's init /atom/proc/attempt_init(...) var/do_initialize = SSatoms.initialized if(do_initialize != INITIALIZATION_INSSATOMS) args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD if(SSatoms.InitAtom(src, args)) // we were deleted return //Called after New if the map is being loaded. mapload = TRUE //Called from base of New if the map is not being loaded. mapload = FALSE //This base must be called or derivatives must set initialized to TRUE //must not sleep //Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE //Must return an Initialize hint. Defined in __DEFINES/subsystems.dm //Note: the following functions don't call the base for optimization and must copypasta: // /turf/Initialize // /turf/open/space/Initialize /atom/proc/Initialize(mapload, ...) SHOULD_CALL_PARENT(TRUE) SHOULD_NOT_SLEEP(TRUE) if(initialized) stack_trace("Warning: [src]([type]) initialized multiple times!") initialized = TRUE if(color) add_atom_colour(color, FIXED_COLOUR_PRIORITY) if(light_power && light_range) update_light() if(opacity && isturf(loc)) var/turf/T = loc T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guranteed to be on afterwards anyways. if(loc) SEND_SIGNAL(loc, COMSIG_ATOM_INITIALIZED_ON, src) // Used for poolcontroller / pool to improve performance greatly. However it also open up path to other usage of observer pattern on turfs. if(length(smoothing_groups)) sortTim(smoothing_groups) //In case it's not properly ordered, let's avoid duplicate entries with the same values. SET_BITFLAG_LIST(smoothing_groups) if(length(canSmoothWith)) sortTim(canSmoothWith) if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF) //If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets. smoothing_flags |= SMOOTH_OBJ SET_BITFLAG_LIST(canSmoothWith) return INITIALIZE_HINT_NORMAL //called if Initialize returns INITIALIZE_HINT_LATELOAD /atom/proc/LateInitialize() SHOULD_NOT_SLEEP(TRUE) return /atom/proc/onCentcom() . = FALSE var/turf/T = get_turf(src) if(!T) return if(!is_admin_level(T.z))//if not, don't bother return //check for centcomm shuttles for(var/centcom_shuttle in list("emergency", "pod1", "pod2", "pod3", "pod4", "ferry")) var/obj/docking_port/mobile/M = SSshuttle.getShuttle(centcom_shuttle) if(T in M.areaInstance) return TRUE //finally check for centcom itself return istype(T.loc, /area/centcom) /atom/proc/onSyndieBase() . = FALSE var/turf/T = get_turf(src) if(!T) return if(!is_admin_level(T.z))//if not, don't bother return if(istype(T.loc, /area/shuttle/syndicate_elite) || istype(T.loc, /area/syndicate_mothership)) return TRUE /atom/Destroy() if(alternate_appearances) for(var/aakey in alternate_appearances) var/datum/alternate_appearance/AA = alternate_appearances[aakey] qdel(AA) alternate_appearances = null REMOVE_FROM_SMOOTH_QUEUE(src) QDEL_NULL(reagents) invisibility = INVISIBILITY_MAXIMUM LAZYCLEARLIST(overlays) LAZYCLEARLIST(priority_overlays) managed_overlays = null QDEL_NULL(light) return ..() //Hook for running code when a dir change occurs /atom/proc/setDir(newdir) SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) dir = newdir /* Sets the atom's pixel locations based on the atom's `dir` variable, and what pixel offset arguments are passed into it If no arguments are supplied, `pixel_x` or `pixel_y` will be set to 0 Used primarily for when players attach mountable frames to walls (APC frame, fire alarm frame, etc.) */ /atom/proc/set_pixel_offsets_from_dir(pixel_north = 0, pixel_south = 0, pixel_east = 0, pixel_west = 0) switch(dir) if(NORTH) pixel_y = pixel_north if(SOUTH) pixel_y = pixel_south if(EAST) pixel_x = pixel_east if(WEST) pixel_x = pixel_west if(NORTHEAST) pixel_y = pixel_north pixel_x = pixel_east if(NORTHWEST) pixel_y = pixel_north pixel_x = pixel_west if(SOUTHEAST) pixel_y = pixel_south pixel_x = pixel_east if(SOUTHWEST) pixel_y = pixel_south pixel_x = pixel_west ///Handle melee attack by a mech /atom/proc/mech_melee_attack(obj/mecha/M) return /atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) if(does_attack_animation) user.changeNext_move(CLICK_CD_MELEE) add_attack_logs(user, src, "Punched with hulk powers") user.do_attack_animation(src, ATTACK_EFFECT_SMASH) /atom/proc/CheckParts(list/parts_list) for(var/A in parts_list) if(istype(A, /datum/reagent)) if(!reagents) reagents = new() reagents.reagent_list.Add(A) reagents.conditional_update() else if(istype(A, /atom/movable)) var/atom/movable/M = A if(isliving(M.loc)) var/mob/living/L = M.loc L.unEquip(M) M.forceMove(src) ///Return the air if we can analyze it /atom/proc/return_analyzable_air() return null /atom/proc/on_reagent_change() return /atom/proc/Bumped(atom/movable/AM) return /// Convenience proc to see if a container is open for chemistry handling /atom/proc/is_open_container() return is_refillable() && is_drainable() /// Is this atom injectable into other atoms /atom/proc/is_injectable(mob/user, allowmobs = TRUE) return reagents && (container_type & (INJECTABLE|REFILLABLE)) /// Can we draw from this atom with an injectable atom /atom/proc/is_drawable(mob/user, allowmobs = TRUE) return reagents && (container_type & (DRAWABLE|DRAINABLE)) /// Can this atoms reagents be refilled /atom/proc/is_refillable() return reagents && (container_type & REFILLABLE) /// Is this atom drainable of reagents /atom/proc/is_drainable() return reagents && (container_type & DRAINABLE) /atom/proc/CheckExit() return TRUE /atom/proc/HasProximity(atom/movable/AM) return /** * Proc which will make the atom act accordingly to an EMP. * This proc can sleep depending on the implementation. So assume it sleeps! * * severity - The severity of the EMP. Either EMP_HEAVY or EMP_LIGHT */ /atom/proc/emp_act(severity) SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) /atom/proc/water_act(volume, temperature, source, method = REAGENT_TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) return TRUE /atom/proc/bullet_act(obj/item/projectile/P, def_zone) SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) . = P.on_hit(src, 0, def_zone) /atom/proc/in_contents_of(container)//can take class or object instance as argument if(ispath(container)) if(istype(src.loc, container)) return TRUE else if(src in container) return TRUE return FALSE /* * atom/proc/search_contents_for(path, list/filter_path = null) * Recursevly searches all atom contens (including contents contents and so on). * * ARGS: path - search atom contents for atoms of this type * list/filter_path - if set, contents of atoms not of types in this list are excluded from search. * * RETURNS: list of found atoms */ /atom/proc/search_contents_for(path, list/filter_path = null) var/list/found = list() for(var/atom/A in src) if(istype(A, path)) found += A if(filter_path) var/pass = 0 for(var/type in filter_path) pass |= istype(A, type) if(!pass) continue if(length(A.contents)) found += A.search_contents_for(path, filter_path) return found /atom/proc/build_base_description(infix = "", suffix = "") //This reformat names to get a/an properly working on item descriptions when they are bloody var/f_name = "\a [src][infix]." if(src.blood_DNA && !istype(src, /obj/effect/decal)) if(gender == PLURAL) f_name = "some " else f_name = "a " if(blood_color != "#030303") f_name += "blood-stained [name][infix]!" else f_name += "oil-stained [name][infix]." . = list("[bicon(src)] That's [f_name] [suffix]") if(desc) . += desc /atom/proc/build_reagent_description(mob/user) . = list() if(!reagents) return var/one_percent = reagents.total_volume / 100 var/blood_type = "" if(user.advanced_reagent_vision()) // You can see absolute unit concentrations in transparent containers and % concentrations in opaque containers. You can also see blood types. . += "It contains:" if(!length(reagents.reagent_list)) . += "Nothing." return if(container_type & TRANSPARENT) for(var/I in reagents.reagent_list) var/datum/reagent/R = I if(R.id != "blood") . += "[R.volume] units of [R] ([round(R.volume / one_percent)]%)" else blood_type = R.data["blood_type"] . += "[R.volume] units of [R] ([blood_type ? "[blood_type]" : ""]) ([round(R.volume / one_percent)]%)" return // Opaque containers for(var/datum/reagent/R in reagents.reagent_list) if(R.id != "blood") . += "[R] ([round(R.volume / one_percent)]%)" else blood_type = R.data["blood_type"] . += "[R] ([blood_type ? "[blood_type]" : ""]) ([round(R.volume / one_percent)]%)" return if(container_type & TRANSPARENT) . += "It contains:" if(user.reagent_vision()) // You can see absolute unit quantities of reagents in transparent containers. for(var/I in reagents.reagent_list) var/datum/reagent/R = I . += "[R.volume] units of [R] ([round(R.volume / one_percent)]%)" return //Otherwise, just show the total volume if(length(reagents?.reagent_list)) . += "[reagents.total_volume] units of various reagents." else . += "Nothing." return if(container_type & AMOUNT_VISIBLE) if(reagents.total_volume) . += "It has [reagents.total_volume] unit\s left." else . += "It's empty." /atom/proc/examine(mob/user, infix = "", suffix = "") . = build_base_description(infix, suffix) . += build_reagent_description(user) SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) /// Extended description of an object. Allows you to double examine objects and have them give you a second description of an item. Useful for writing flavourful stuff. /atom/proc/examine_more(mob/user) SHOULD_CALL_PARENT(TRUE) RETURN_TYPE(/list) . = list() SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) /** * Updates the appearence of the icon * * Mostly delegates to update_name, update_desc, and update_icon * * Arguments: * - updates: A set of bitflags dictating what should be updated. Defaults to [ALL] */ /atom/proc/update_appearance(updates=ALL) SHOULD_NOT_SLEEP(TRUE) SHOULD_CALL_PARENT(TRUE) /// Signal sent should the appearance be updated. This is more broad if listening to a more specific signal doesn't cut it updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_APPEARANCE, updates) if(updates & UPDATE_NAME) update_name(updates) if(updates & UPDATE_DESC) update_desc(updates) if(updates & UPDATE_ICON) update_icon(updates) /// Updates the name of the atom /atom/proc/update_name(updates=ALL) SHOULD_CALL_PARENT(TRUE) return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_NAME, updates) /// Updates the description of the atom /atom/proc/update_desc(updates=ALL) SHOULD_CALL_PARENT(TRUE) return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_DESC, updates) /// Updates the icon of the atom /atom/proc/update_icon(updates=ALL) SIGNAL_HANDLER SHOULD_CALL_PARENT(TRUE) if(updates & NONE) return // NONE is being sent on purpose, and thus no signal should be sent. updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates) if(updates & UPDATE_ICON_STATE) update_icon_state() SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE) if(updates & UPDATE_OVERLAYS) var/list/new_overlays = update_overlays(updates) if(managed_overlays) cut_overlay(managed_overlays) managed_overlays = null if(length(new_overlays)) if(length(new_overlays) == 1) managed_overlays = new_overlays[1] else managed_overlays = new_overlays add_overlay(new_overlays) SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS) SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates) /// Updates the icon state of the atom /atom/proc/update_icon_state() return /// Updates the overlays of the atom. It has to return a list of overlays if it can't call the parent to create one. The list can contain anything that would be valid for the add_overlay proc: Images, mutable appearances, icon states... /atom/proc/update_overlays() return list() /atom/proc/relaymove() return /atom/proc/ex_act() return /atom/proc/blob_act(obj/structure/blob/B) SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) /atom/proc/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) if(reagents) reagents.temperature_reagents(exposed_temperature) /// If it returns TRUE, attack chain stops /atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) switch(tool_type) if(TOOL_CROWBAR) return crowbar_act(user, I) if(TOOL_MULTITOOL) return multitool_act(user, I) if(TOOL_SCREWDRIVER) return screwdriver_act(user, I) if(TOOL_WRENCH) return wrench_act(user, I) if(TOOL_WIRECUTTER) return wirecutter_act(user, I) if(TOOL_WELDER) return welder_act(user, I) // Tool-specific behavior procs. To be overridden in subtypes. /atom/proc/crowbar_act(mob/living/user, obj/item/I) return /atom/proc/multitool_act(mob/living/user, obj/item/I) return //Check if the multitool has an item in its data buffer /atom/proc/multitool_check_buffer(user, silent = FALSE) if(!silent) to_chat(user, "[src] has no data buffer!") return FALSE /atom/proc/screwdriver_act(mob/living/user, obj/item/I) return /atom/proc/wrench_act(mob/living/user, obj/item/I) return /atom/proc/wirecutter_act(mob/living/user, obj/item/I) return /atom/proc/welder_act(mob/living/user, obj/item/I) return /// This is when an atom is emagged. Should return false if it fails, or it has no emag_act defined. /atom/proc/emag_act(mob/user) SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user) return FALSE /atom/proc/unemag() return /atom/proc/cmag_act(mob/user) return /atom/proc/uncmag() return /** * Respond to a radioactive wave hitting this atom * * Default behaviour is to send [COMSIG_ATOM_RAD_ACT] and return */ /atom/proc/rad_act(amount) SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, amount) /atom/proc/fart_act(mob/living/M) return FALSE /atom/proc/rpd_act() return /atom/proc/rpd_blocksusage() // Atoms that return TRUE prevent RPDs placing any kind of pipes on their turf. return FALSE /atom/proc/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) SEND_SIGNAL(src, COMSIG_ATOM_HITBY, hitting_atom, skipcatch, hitpush, blocked, throwingdatum) if(density && !has_gravity(hitting_atom)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). addtimer(CALLBACK(src, PROC_REF(hitby_react), hitting_atom), 2) /// This proc applies special effects of a carbon mob hitting something, be it a wall, structure, or window. You can set mob_hurt to false to avoid double dipping through subtypes if returning ..() /atom/proc/hit_by_thrown_mob(mob/living/C, datum/thrownthing/throwingdatum, damage, mob_hurt = FALSE, self_hurt = FALSE) return /atom/proc/hitby_react(atom/movable/AM) if(AM && isturf(AM.loc)) step(AM, turn(AM.dir, 180)) /atom/proc/add_filter(name, priority, list/params) LAZYINITLIST(filter_data) var/list/p = params.Copy() p["priority"] = priority filter_data[name] = p update_filters() /atom/proc/update_filters() filters = null filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) for(var/f in filter_data) var/list/data = filter_data[f] var/list/arguments = data.Copy() arguments -= "priority" filters += filter(arglist(arguments)) UNSETEMPTY(filter_data) /atom/proc/transition_filter(name, time, list/new_params, easing = LINEAR_EASING, loop = 1) var/filter = get_filter(name) if(!filter) return var/list/old_filter_data = filter_data[name] var/list/params = old_filter_data.Copy() for(var/thing in new_params) params[thing] = new_params[thing] animate(filter, new_params, time = time, easing = easing, loop = loop) for(var/param in params) filter_data[name][param] = params[param] /atom/proc/change_filter_priority(name, new_priority) if(!filter_data || !filter_data[name]) return filter_data[name]["priority"] = new_priority update_filters() /obj/item/update_filters() . = ..() for(var/X in actions) var/datum/action/A = X A.UpdateButtons() /atom/proc/get_filter(name) if(filter_data && filter_data[name]) return filters[filter_data.Find(name)] /atom/proc/remove_filter(name_or_names) if(!filter_data) return var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names) for(var/name in names) if(filter_data[name]) filter_data -= name update_filters() /atom/proc/clear_filters() filter_data = null filters = null /* * Base proc, terribly named but it's all over the code so who cares I guess right? * * Returns FALSE by default, if a child returns TRUE it is implied that the atom has in * some way done a spooky thing. Current usage is so that Boo knows if it needs to cool * down or not, but this could be expanded upon if you were a bad enough dude. */ /atom/proc/get_spooked() return FALSE /** Base proc, intended to be overriden. This should only be called from one place: inside the slippery component. Called after a human mob slips on this atom. If you want the person who slipped to have something special done to them, put it here. */ /atom/proc/after_slip(mob/living/carbon/human/H) return /atom/proc/add_hiddenprint(mob/living/M) if(isnull(M)) return if(isnull(M.key)) return if(ishuman(M)) var/mob/living/carbon/human/H = M if(!istype(H.dna, /datum/dna)) return FALSE if(H.gloves) if(fingerprintslast != H.ckey) //Add the list if it does not exist. if(!fingerprintshidden) fingerprintshidden = list() fingerprintshidden += "\[[all_timestamps()]\] (Wearing gloves). Real name: [H.real_name], Key: [H.key]" fingerprintslast = H.ckey return FALSE if(!fingerprints) if(fingerprintslast != H.ckey) //Add the list if it does not exist. if(!fingerprintshidden) fingerprintshidden = list() fingerprintshidden += "\[[all_timestamps()]\] Real name: [H.real_name], Key: [H.key]" fingerprintslast = H.ckey return TRUE else if(fingerprintslast != M.ckey) //Add the list if it does not exist. if(!fingerprintshidden) fingerprintshidden = list() fingerprintshidden += "\[[all_timestamps()]\] Real name: [M.real_name], Key: [M.key]" fingerprintslast = M.ckey return //Set ignoregloves to add prints irrespective of the mob having gloves on. /atom/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) if(isnull(M)) return if(isnull(M.key)) return if(ishuman(M)) //Add the list if it does not exist. if(!fingerprintshidden) fingerprintshidden = list() //Fibers~ add_fibers(M) //He has no prints! if(HAS_TRAIT(M, TRAIT_NOFINGERPRINTS)) if(fingerprintslast != M.key) fingerprintshidden += "(Has no fingerprints) Real name: [M.real_name], Key: [M.key]" fingerprintslast = M.key return FALSE //Now, lets get to the dirty work. //First, make sure their DNA makes sense. var/mob/living/carbon/human/H = M if(!istype(H.dna, /datum/dna) || !H.dna.uni_identity || (length(H.dna.uni_identity) != 32)) if(!istype(H.dna, /datum/dna)) H.dna = new /datum/dna(null) H.dna.real_name = H.real_name H.check_dna() //Check if the gloves (if any) hide fingerprints if(H.gloves) var/obj/item/clothing/gloves/G = H.gloves if(G.transfer_prints) ignoregloves = TRUE //Now, deal with gloves. if(!ignoregloves) if(H.gloves && H.gloves != src) if(fingerprintslast != H.ckey) fingerprintshidden += "\[[all_timestamps()]\] (Wearing gloves). Real name: [H.real_name], Key: [H.key]" fingerprintslast = H.ckey return FALSE //More adminstuffz if(fingerprintslast != H.ckey) fingerprintshidden += "\[[all_timestamps()]\] Real name: [H.real_name], Key: [H.key]" fingerprintslast = H.ckey //Make the list if it does not exist. if(!fingerprints) fingerprints = list() //Hash this shit. var/full_print = H.get_full_print() // Add the fingerprints fingerprints[full_print] = full_print return TRUE else //Smudge up dem prints some if(fingerprintslast != M.ckey) fingerprintshidden += "\[[all_timestamps()]\] Real name: [M.real_name], Key: [M.key]" fingerprintslast = M.ckey return /atom/proc/transfer_fingerprints_to(atom/A) // Make sure everything are lists. if(!islist(A.fingerprints)) A.fingerprints = list() if(!islist(A.fingerprintshidden)) A.fingerprintshidden = list() if(!islist(fingerprints)) fingerprints = list() if(!islist(fingerprintshidden)) fingerprintshidden = list() // Transfer if(fingerprints) A.fingerprints |= fingerprints.Copy() //detective if(fingerprintshidden) A.fingerprintshidden |= fingerprintshidden.Copy() //admin A.fingerprintslast = fingerprintslast GLOBAL_LIST_EMPTY(blood_splatter_icons) //returns the mob's dna info as a list, to be inserted in an object's blood_DNA list /mob/living/proc/get_blood_dna_list() if(get_blood_id() != "blood") return return list("ANIMAL DNA" = "Y-") /mob/living/carbon/get_blood_dna_list() if(get_blood_id() != "blood") return var/list/blood_dna = list() if(dna) blood_dna[dna.unique_enzymes] = dna.blood_type else blood_dna["UNKNOWN DNA"] = "X*" return blood_dna /mob/living/carbon/alien/get_blood_dna_list() return list("UNKNOWN DNA" = "X*") //to add a mob's dna info into an object's blood_DNA list. /atom/proc/transfer_mob_blood_dna(mob/living/L) var/new_blood_dna = L.get_blood_dna_list() if(!new_blood_dna) return FALSE return transfer_blood_dna(new_blood_dna) /obj/effect/decal/cleanable/blood/splatter/transfer_mob_blood_dna(mob/living/L) ..(L) var/list/b_data = L.get_blood_data(L.get_blood_id()) if(b_data && !isnull(b_data["blood_color"])) basecolor = b_data["blood_color"] else basecolor = "#A10808" update_icon() /obj/effect/decal/cleanable/blood/footprints/transfer_mob_blood_dna(mob/living/L) ..(L) var/list/b_data = L.get_blood_data(L.get_blood_id()) if(b_data && !isnull(b_data["blood_color"])) basecolor = b_data["blood_color"] else basecolor = "#A10808" update_icon() //to add blood dna info to the object's blood_DNA list /atom/proc/transfer_blood_dna(list/blood_dna) if(!blood_DNA) blood_DNA = list() var/old_length = length(blood_DNA) blood_DNA |= blood_dna if(length(blood_DNA) > old_length) return TRUE//some new blood DNA was added //to add blood from a mob onto something, and transfer their dna info /atom/proc/add_mob_blood(mob/living/M) var/list/blood_dna = M.get_blood_dna_list() if(!blood_dna) return FALSE var/bloodcolor = "#A10808" var/list/b_data = M.get_blood_data(M.get_blood_id()) if(b_data) bloodcolor = b_data["blood_color"] return add_blood(blood_dna, bloodcolor) //to add blood onto something, with blood dna info to include. /atom/proc/add_blood(list/blood_dna, b_color) return FALSE /obj/item/add_blood(list/blood_dna, b_color) if(isnull(b_color)) b_color = "#A10808" var/blood_count = !blood_DNA ? 0 : length(blood_DNA) if(!transfer_blood_dna(blood_dna)) return FALSE blood_color = b_color // update the blood color if(!blood_count) //apply the blood-splatter overlay if it isn't already in there add_blood_overlay() return TRUE //we applied blood to the item /* * This proc makes src gloves bloody, if you touch something with them you will leave a blood trace * blood_dna: list of blood DNAs stored in each atom in blood_DNA variable or in get_blood_dna_list() on carbons * b_color: blood color, simple. If there will be null, the blood will be red, otherwise the color you pass * amount: amount of "blood charges" you want to give to the gloves, that will be used to make items/walls bloody. You can make something bloody this amount - 1 times. If this variable will be null, amount will be set randomly from 2 to max_amount * max_amount: if amount is not set, amount will be random from 2 to this value, default 4 */ /obj/item/clothing/gloves/add_blood(list/blood_dna, b_color, amount, max_amount = 4) . = ..() if(isnull(amount)) transfer_blood = rand(2, max_amount) else transfer_blood = max(1, amount) /turf/add_blood(list/blood_dna, b_color) if(isnull(b_color)) b_color = "#A10808" var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src if(!B) B = new /obj/effect/decal/cleanable/blood/splatter(src) B.transfer_blood_dna(blood_dna) //give blood info to the blood decal. B.basecolor = b_color return TRUE //we bloodied the floor /mob/living/carbon/human/add_blood(list/blood_dna, b_color) if(wear_suit) wear_suit.add_blood(blood_dna, b_color) update_inv_wear_suit() else if(w_uniform) w_uniform.add_blood(blood_dna, b_color) update_inv_w_uniform() if(head) head.add_blood(blood_dna, b_color) update_inv_head() if(glasses) glasses.add_blood(blood_dna, b_color) update_inv_glasses() if(gloves) var/obj/item/clothing/gloves/G = gloves G.add_blood(blood_dna, b_color) add_verb(src, /mob/living/carbon/human/proc/bloody_doodle) else hand_blood_color = b_color bloody_hands = rand(2, 4) transfer_blood_dna(blood_dna) add_verb(src, /mob/living/carbon/human/proc/bloody_doodle) update_inv_gloves() //handles bloody hands overlays and updating return TRUE /obj/item/proc/add_blood_overlay() if(initial(icon) && initial(icon_state)) var/list/params = GLOB.blood_splatter_icons["[blood_color]"] if(!params) params = layering_filter(icon = icon('icons/effects/blood.dmi', "itemblood"), color = blood_color, blend_mode = BLEND_INSET_OVERLAY) GLOB.blood_splatter_icons["[blood_color]"] = params add_filter("blood_splatter", 1, params) /atom/proc/clean_blood(radiation_clean = FALSE) germ_level = 0 if(radiation_clean) clean_radiation() if(islist(blood_DNA)) blood_DNA = null return TRUE /** * Removes some radiation from an atom * * Removes a configurable amount of radiation from an atom * and stops green glow if radiation gets low enough through it. * Arguments: * * clean_factor - How much radiation to remove, as a multiple of RAD_BACKGROUND_RADIATION (currently 9) */ /atom/proc/clean_radiation(clean_factor = 2) var/datum/component/radioactive/healthy_green_glow = GetComponent(/datum/component/radioactive) if(!QDELETED(healthy_green_glow)) healthy_green_glow.strength = max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * clean_factor))) if(healthy_green_glow.strength <= RAD_BACKGROUND_RADIATION) healthy_green_glow.RemoveComponent() /obj/effect/decal/cleanable/blood/clean_blood(radiation_clean = FALSE) return // While this seems nonsensical, clean_blood isn't supposed to be used like this on a blood decal. /obj/item/clean_blood(radiation_clean = FALSE) . = ..() if(.) if(initial(icon) && initial(icon_state)) remove_filter("blood_splatter") /obj/item/clothing/gloves/clean_blood(radiation_clean = FALSE) . = ..() if(.) transfer_blood = 0 /obj/item/clothing/shoes/clean_blood(radiation_clean = FALSE) ..() bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) blood_state = BLOOD_STATE_NOT_BLOODY if(ismob(loc)) var/mob/M = loc M.update_inv_shoes() /mob/living/carbon/human/clean_blood(radiation_clean = FALSE, clean_hands = TRUE, clean_mask = TRUE, clean_feet = TRUE) if(w_uniform && !(wear_suit && wear_suit.flags_inv & HIDEJUMPSUIT)) if(w_uniform.clean_blood(radiation_clean)) update_inv_w_uniform() if(gloves && !(wear_suit && wear_suit.flags_inv & HIDEGLOVES)) if(gloves.clean_blood(radiation_clean)) update_inv_gloves() gloves.germ_level = 0 clean_hands = FALSE if(shoes && !(wear_suit && wear_suit.flags_inv & HIDESHOES)) if(shoes.clean_blood(radiation_clean)) update_inv_shoes() clean_feet = FALSE if(s_store && !(wear_suit && wear_suit.flags_inv & HIDESUITSTORAGE)) if(s_store.clean_blood(radiation_clean)) update_inv_s_store() if(lip_style && !(head && head.flags_inv & HIDEMASK)) lip_style = null update_body() if(glasses && !(wear_mask && wear_mask.flags_inv & HIDEEYES)) if(glasses.clean_blood(radiation_clean)) update_inv_glasses() if(l_ear && !(wear_mask && wear_mask.flags_inv & HIDEEARS)) if(l_ear.clean_blood(radiation_clean)) update_inv_ears() if(r_ear && !(wear_mask && wear_mask.flags_inv & HIDEEARS)) if(r_ear.clean_blood(radiation_clean)) update_inv_ears() if(belt) if(belt.clean_blood(radiation_clean)) update_inv_belt() ..(clean_hands, clean_mask, clean_feet) update_icons() //apply the now updated overlays to the mob /atom/proc/add_vomit_floor(toxvomit = FALSE, green = FALSE) playsound(src, 'sound/effects/splat.ogg', 50, TRUE) if(!isspaceturf(src)) var/type = green ? /obj/effect/decal/cleanable/vomit/green : /obj/effect/decal/cleanable/vomit var/vomit_reagent = green ? "green_vomit" : "vomit" for(var/obj/effect/decal/cleanable/vomit/V in get_turf(src)) if(V.type == type) V.reagents.add_reagent(vomit_reagent, 5) return var/obj/effect/decal/cleanable/vomit/this = new type(src) if(!this.gravity_check) this.newtonian_move(dir) // Make toxins vomit look different if(toxvomit) this.icon_state = "vomittox_[pick(1, 4)]" /atom/proc/get_global_map_pos() if(!islist(GLOB.global_map) || isemptylist(GLOB.global_map)) return var/cur_x = null var/cur_y = null var/list/y_arr = null for(cur_x in 1 to length(GLOB.global_map)) y_arr = GLOB.global_map[cur_x] cur_y = y_arr.Find(src.z) if(cur_y) break // to_chat(world, "X = [cur_x]; Y = [cur_y]") if(cur_x && cur_y) return list("x" = cur_x, "y" = cur_y) else return null // Used to provide overlays when using this atom as a viewing focus // (cameras, locker tint, etc.) /atom/proc/get_remote_view_fullscreens(mob/user) return //the sight changes to give to the mob whose perspective is set to that atom (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) /atom/proc/update_remote_sight(mob/living/user) user.sync_lighting_plane_alpha() return /atom/proc/checkpass(passflag) return pass_flags & passflag /atom/proc/isinspace() if(isspaceturf(get_turf(src))) return TRUE else return FALSE /atom/proc/handle_fall() return /atom/proc/singularity_act() return /atom/proc/singularity_pull(obj/singularity/S, current_size) SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) /** * Respond to acid being used on our atom * * Default behaviour is to send COMSIG_ATOM_ACID_ACT and return */ /atom/proc/acid_act(acidpwr, acid_volume) SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) /atom/proc/narsie_act() return /** * Respond to an electric bolt action on our item * * Default behaviour is to return, we define here to allow for cleaner code later on */ /atom/proc/zap_act(power, zap_flags) return /atom/proc/handle_ricochet(obj/item/projectile/ricocheting_projectile) var/turf/p_turf = get_turf(ricocheting_projectile) var/face_direction = get_dir(src, p_turf) || get_dir(src, ricocheting_projectile) var/face_angle = dir2angle(face_direction) var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.Angle + 180)) var/a_incidence_s = abs(incidence_s) if(a_incidence_s > 90 && a_incidence_s < 270) return FALSE if((ricocheting_projectile.flag in list(BULLET, BOMB)) && ricocheting_projectile.ricochet_incidence_leeway) if((a_incidence_s < 90 && a_incidence_s < 90 - ricocheting_projectile.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > ricocheting_projectile.ricochet_incidence_leeway)) return FALSE var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) ricocheting_projectile.set_angle(new_angle_s) return TRUE //This proc is called on the location of an atom when the atom is Destroy()'d /atom/proc/handle_atom_del(atom/A) return /atom/proc/atom_say(message) if(!message) return var/list/speech_bubble_hearers = list() for(var/mob/M as anything in get_mobs_in_view(7, src)) M.show_message("[src] [atom_say_verb], \"[message]\"", 2, null, 1) if(M.client) speech_bubble_hearers += M.client if((M.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) && M.can_hear()) M.create_chat_message(src, message) if(length(speech_bubble_hearers)) var/image/I = image('icons/mob/talk.dmi', src, "[bubble_icon][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay), I, speech_bubble_hearers, 30) /atom/proc/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list()) return /atom/vv_edit_var(var_name, var_value) . = ..() switch(var_name) if("light_power", "light_range", "light_color") update_light() if("color") add_atom_colour(color, ADMIN_COLOUR_PRIORITY) /atom/vv_get_dropdown() . = ..() .["Manipulate Colour Matrix"] = "?_src_=vars;manipcolours=[UID()]" var/turf/curturf = get_turf(src) if(curturf) .["Jump to turf"] = "?_src_=holder;adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" .["Add reagent"] = "?_src_=vars;addreagent=[UID()]" .["Edit reagents"] = "?_src_=vars;editreagents=[UID()]" .["Trigger explosion"] = "?_src_=vars;explode=[UID()]" .["Trigger EM pulse"] = "?_src_=vars;emp=[UID()]" /atom/proc/AllowDrop() return FALSE /atom/proc/drop_location() var/atom/L = loc if(!L) return null return L.AllowDrop() ? L : get_turf(L) /atom/Entered(atom/movable/AM, atom/oldLoc) SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) /atom/Exit(atom/movable/AM, atom/newLoc) . = ..() if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) return FALSE /atom/Exited(atom/movable/AM, atom/newLoc) SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) /* Adds an instance of colour_type to the atom's atom_colours list */ /atom/proc/add_atom_colour(coloration, colour_priority) if(!atom_colours || !length(atom_colours)) atom_colours = list() atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. if(!coloration) return if(colour_priority > length(atom_colours)) return atom_colours[colour_priority] = coloration update_atom_colour() /* Removes an instance of colour_type from the atom's atom_colours list */ /atom/proc/remove_atom_colour(colour_priority, coloration) if(!atom_colours) atom_colours = list() atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. if(colour_priority > length(atom_colours)) return if(coloration && atom_colours[colour_priority] != coloration) return //if we don't have the expected color (for a specific priority) to remove, do nothing atom_colours[colour_priority] = null update_atom_colour() /* Resets the atom's color to null, and then sets it to the highest priority colour available */ /atom/proc/update_atom_colour() if(!atom_colours) atom_colours = list() atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. color = null for(var/C in atom_colours) if(islist(C)) var/list/L = C if(length(L)) color = L return else if(C) color = C return /** Call this when you want to present a renaming prompt to the user. It's a simple proc, but handles annoying edge cases such as forgetting to add a "cancel" button, or being able to rename stuff remotely. Arguments: * user - the renamer. * implement - the tool doing the renaming (usually, a pen). * use_prefix - whether the new name should follow the format of "thing - user-given label" or if we allow to change the name completely arbitrarily. * actually_rename - whether we want to really change the `src.name`, or if we want to do everything *except* that. * prompt - a custom "what do you want rename this thing to be?" prompt shown in the inpit box. Returns: Either null if the renaming was aborted, or the user-provided sanitized string. **/ /atom/proc/rename_interactive(mob/user, obj/implement = null, use_prefix = TRUE, actually_rename = TRUE, prompt = null) // Sanity check that the user can, indeed, rename the thing. // This, sadly, means you can't rename things with a telekinetic pen, but that's // too much of a hassle to make work nicely. if((implement && implement.loc != user) || !in_range(src, user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) return null var/prefix = "" if(use_prefix) prefix = "[initial(name)] - " var/default_value if(!use_prefix) default_value = name else if(findtext(name, prefix) != 0) default_value = copytext_char(name, length_char(prefix) + 1) else // Either the thing has a non-conforming name due to being set in the map // OR (much more likely) the thing is unlabeled yet. default_value = "" if(!prompt) prompt = "What would you like the label on [src] to be?" var/t = input(user, prompt, "Renaming [src]", default_value) as text | null if(isnull(t)) // user pressed Cancel return null // Things could have changed between when `input` is called and when it returns. if(!user) return null else if(implement && implement.loc != user) to_chat(user, "You no longer have the pen to rename [src].") return null else if(!in_range(src, user)) to_chat(user, "You cannot rename [src] from here.") return null else if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) to_chat(user, "You cannot rename [src] in your current state.") return null t = sanitize(copytext_char(t, 1, MAX_NAME_LEN)) // Logging var/logged_name = initial(name) if(t) logged_name = "[use_prefix ? "[prefix][t]" : t]" investigate_log("[key_name(user)] ([ADMIN_FLW(user,"FLW")]) renamed \"[src]\" ([ADMIN_VV(src, "VV")]) as \"[logged_name]\".", INVESTIGATE_RENAME) if(actually_rename) if(t == "") name = "[initial(name)]" else name = "[prefix][t]" return t /atom/proc/set_angle(degrees) var/matrix/M = matrix() M.Turn(degrees) // If we aint 0, make it NN transform if(degrees) appearance_flags |= PIXEL_SCALE transform = M /* Setter for the `density` variable. Arguments: * new_value - the new density you would want it to set. Returns: Either null if identical to existing density, or the new density if different. */ /atom/proc/set_density(new_value) if(density == new_value) return . = density density = new_value /** * This proc is used for telling whether something can pass by this atom in a given direction, for use by the pathfinding system. * * Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely * multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible. * * For turfs this will only be used if pathing_pass_method is TURF_PATHING_PASS_PROC * * Arguments: * * to_dir - What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions * * pass_info - Datum that stores info about the thing that's trying to pass us * * IMPORTANT NOTE: /turf/proc/LinkBlockedWithAccess assumes that overrides of CanPathfindPass will always return true if density is FALSE * If this is NOT you, ensure you edit your can_pathfind_pass variable. Check __DEFINES/path.dm **/ /atom/proc/CanPathfindPass(to_dir, datum/can_pass_info/pass_info) if(pass_info.pass_flags & pass_flags_self) return TRUE . = !density /atom/proc/atom_prehit(obj/item/projectile/P) return SEND_SIGNAL(src, COMSIG_ATOM_PREHIT, P) /// Passes Stat Browser Panel clicks to the game and calls client click on an atom /atom/Topic(href, list/href_list) . = ..() if(!usr?.client) return if(href_list["statpanel_item_click"]) var/client/usr_client = usr.client var/list/paramslist = list() switch(href_list["statpanel_item_click"]) if("left") paramslist[LEFT_CLICK] = "1" if("right") paramslist[RIGHT_CLICK] = "1" if("middle") paramslist[MIDDLE_CLICK] = "1" else return if(href_list["statpanel_item_shiftclick"]) paramslist[SHIFT_CLICK] = "1" if(href_list["statpanel_item_ctrlclick"]) paramslist[CTRL_CLICK] = "1" if(href_list["statpanel_item_altclick"]) paramslist[ALT_CLICK] = "1" var/mouseparams = list2params(paramslist) usr_client.Click(src, loc, null, mouseparams) return TRUE /** * A special case of relaymove() in which the person relaying the move may be "driving" this atom * * This is a special case for vehicles and ridden animals where the relayed movement may be handled * by the riding component attached to this atom. Returns TRUE as long as there's nothing blocking * the movement, or FALSE if the signal gets a reply that specifically blocks the movement */ /atom/proc/relaydrive(mob/living/user, direction) return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE)