/** * The base type for nearly all physical objects in SS13 * Lots and lots of functionality lives here, although in general we are striving to move * as much as possible to the components/elements system */ /atom abstract_type = /atom layer = ABOVE_NORMAL_TURF_LAYER plane = GAME_PLANE appearance_flags = TILE_BOUND|LONG_GLIDE /// 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 ///First atom flags var var/flags_1 = NONE ///Intearaction flags var/interaction_flags_atom = NONE var/flags_ricochet = NONE ///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 ///Reagents holder var/datum/reagents/reagents = null ///all of this atom's HUD (med/sec, etc) images. Associative list of the form: list(hud category = hud image or images for that category). ///most of the time hud category is associated with a single image, sometimes its associated with a list of images. ///not every hud in this list is actually used. for ones available for others to see, look at active_hud_list. var/list/image/hud_list = null ///all of this atom's HUD images which can actually be seen by players with that hud var/list/image/active_hud_list = null ///HUD images that this atom can provide. var/list/hud_possible ///How much this atom resists explosions by, in the end var/explosive_resistance = 0 ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays. var/list/managed_vis_overlays /// Lazylist of all images (or atoms, I'm sorry) (hopefully attached to us) to update when we change z levels /// You will need to manage adding/removing from this yourself, but I'll do the updating for you var/list/image/update_on_z /// Lazylist of all overlays attached to us to update when we change z levels /// You will need to manage adding/removing from this yourself, but I'll do the updating for you /// Oh and note, if order of addition is important this WILL break that. so mind yourself var/list/image/update_overlays_on_z ///Cooldown tick timer for buckle messages var/buckle_message_cooldown = 0 ///Last fingerprints to touch this atom var/fingerprintslast /// Radiation insulation types var/rad_insulation = RAD_NO_INSULATION /// The icon state intended to be used for the acid component. Used to override the default acid overlay icon state. var/custom_acid_overlay = null var/datum/wires/wires = null ///Light systems, both shouldn't be active at the same time. var/light_system = COMPLEX_LIGHT ///Range of the light in tiles. Zero means no light. var/light_range = 0 ///Intensity of the light. The stronger, the less shadows you will see on the lit area. var/light_power = 1 ///Hexadecimal RGB string representing the colour of the light. White by default. var/light_color = COLOR_WHITE /// Angle of light to show in light_dir /// 360 is a circle, 90 is a cone, etc. var/light_angle = 360 /// What angle to project light in var/light_dir = NORTH ///Boolean variable for toggleable lights. Has no effect without the proper light_system, light_range and light_power values. var/light_on = TRUE /// How many tiles "up" this light is. 1 is typical, should only really change this if it's a floor light var/light_height = LIGHTING_HEIGHT ///Bitflags to determine lighting-related atom properties. var/light_flags = NONE ///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 /// Last name used to calculate a color for the chatmessage overlays var/chat_color_name /// Last color calculated for the the chatmessage overlays var/chat_color /// A luminescence-shifted value of the last color calculated for chatmessage overlays var/chat_color_darkened // Use SET_BASE_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_x and y for you ///Default pixel x shifting for the atom's icon. var/base_pixel_x = 0 ///Default pixel y shifting for the atom's icon. var/base_pixel_y = 0 // Use SET_BASE_VISUAL_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_w and z for you ///Default pixel w shifting for the atom's icon. var/base_pixel_w = 0 ///Default pixel z shifting for the atom's icon. var/base_pixel_z = 0 ///Used for changing icon states for different base sprites. var/base_icon_state ///Icon-smoothing behavior. var/smoothing_flags = NONE ///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 ///What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it. Must be sorted. 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. Must be sorted. var/list/canSmoothWith = null ///AI controller that controls this atom. type on init, then turned into an instance during runtime var/datum/ai_controller/ai_controller /// forensics datum, contains fingerprints, fibres, blood_dna and hiddenprints on this atom var/datum/forensics/forensics = null /// Cached color for all blood on us to avoid doing constant math var/cached_blood_color = null /// Cached emissive alpha for all blood on us to avoid doing constant math var/cached_blood_emissive = null /// How this atom should react to having its astar blocking checked var/can_astar_pass = CANASTARPASS_DENSITY ///whether ghosts can see screentips on it var/ghost_screentips = FALSE /// Flags to check for in can_perform_action. Used in alt-click & ctrl-click checks var/interaction_flags_click = NONE /// Flags to check for in can_perform_action for mouse drag & drop checks. To bypass checks see interaction_flags_atom mouse drop flags var/interaction_flags_mouse_drop = NONE /// Generally for niche objects, atoms blacklisted can spawn if enabled by spawner. var/spawn_blacklisted = FALSE /** * Top level of the destroy chain for most atoms * * Cleans up the following: * * Removes alternate apperances from huds that see them * * qdels the reagent holder from atoms if it exists * * clears the orbiters list * * clears overlays and priority overlays * * clears the light object */ /atom/Destroy(force) if(alternate_appearances) for(var/current_alternate_appearance in alternate_appearances) var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance] selected_alternate_appearance.remove_atom_from_hud(src) if(reagents) QDEL_NULL(reagents) if(forensics) QDEL_NULL(forensics) if(atom_storage) QDEL_NULL(atom_storage) if(wires) QDEL_NULL(wires) orbiters = null // The component is attached to us normaly and will be deleted elsewhere // Checking length(overlays) before cutting has significant speed benefits if (length(overlays)) overlays.Cut() LAZYNULL(managed_overlays) if(ai_controller) QDEL_NULL(ai_controller) if(light) QDEL_NULL(light) if (length(light_sources)) light_sources.Cut() if(smoothing_flags & SMOOTH_QUEUED) SSicon_smooth.remove_from_queues(src) #ifndef DISABLE_DREAMLUAU // These lists cease existing when src does, so we need to clear any lua refs to them that exist. if(!(datum_flags & DF_STATIC_OBJECT)) DREAMLUAU_CLEAR_REF_USERDATA(contents) DREAMLUAU_CLEAR_REF_USERDATA(filters) DREAMLUAU_CLEAR_REF_USERDATA(overlays) DREAMLUAU_CLEAR_REF_USERDATA(underlays) #endif return ..() /atom/proc/handle_ricochet(obj/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.armor_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 /// Whether the mover object can avoid being blocked by this atom, while arriving from (or leaving through) the border_dir. /atom/proc/CanPass(atom/movable/mover, border_dir) SHOULD_CALL_PARENT(TRUE) SHOULD_BE_PURE(TRUE) if(SEND_SIGNAL(src, COMSIG_ATOM_TRIED_PASS, mover, border_dir) & COMSIG_COMPONENT_PERMIT_PASSAGE) return TRUE if(mover.movement_type & PHASING) return TRUE . = CanAllowThrough(mover, border_dir) // This is cheaper than calling the proc every time since most things dont override CanPassThrough if(!mover.generic_canpass) return mover.CanPassThrough(src, REVERSE_DIR(border_dir), .) /// Returns true or false to allow the mover to move through src /atom/proc/CanAllowThrough(atom/movable/mover, border_dir) SHOULD_CALL_PARENT(TRUE) //SHOULD_BE_PURE(TRUE) if(mover.pass_flags & pass_flags_self) return TRUE if(mover.throwing && (pass_flags_self & LETPASSTHROW)) return TRUE return !density /** * Is this atom currently located on centcom (or riding off into the sunset on a shuttle) * * Specifically, is it on the z level and within the centcom areas. * You can also be in a shuttle during endgame transit. * * Used in gamemode to identify mobs who have escaped and for some other areas of the code * who don't want atoms where they shouldn't be * * Returns TRUE if this atom is on centcom or an escape shuttle, or FALSE if not */ /atom/proc/onCentCom() var/turf/current_turf = get_turf(src) if(!current_turf) return FALSE // This doesn't necessarily check that we're at central command, // but it checks for any shuttles which have finished are still in hyperspace // (IE, stuff like the whiteship which fly off into the sunset and "escape") if(is_reserved_level(current_turf.z)) return on_escaped_shuttle(ENDGAME_TRANSIT) // From here on we only concern ourselves with people actually on the centcom Z if(!is_centcom_level(current_turf.z)) return FALSE if(istype(current_turf.loc, /area/centcom)) return TRUE // Finally, check if we're on an escaped shuttle return on_escaped_shuttle() /** * Is the atom in any of the syndicate areas * * Either in the syndie base, or any of their shuttles * * Also used in gamemode code for win conditions * * Returns TRUE if this atom is on the syndicate recon base, any of its shuttles, or an escape shuttle, or FALSE if not */ /atom/proc/onSyndieBase() var/turf/current_turf = get_turf(src) if(!current_turf) return FALSE // Syndicate base is loaded in a reserved level. If not reserved, we don't care. if(!is_reserved_level(current_turf.z)) return FALSE var/static/list/syndie_typecache = typecacheof(list( /area/centcom/syndicate_mothership, // syndicate base itself /area/shuttle/assault_pod, // steel rain /area/shuttle/syndicate, // infiltrator )) if(is_type_in_typecache(current_turf.loc, syndie_typecache)) return TRUE // Finally, check if we're on an escaped shuttle return on_escaped_shuttle() /** * Checks that we're on a shuttle that's escaped * * * check_for_launch_status - What launch status do we check for? Generally the two you want to check for are ENDGAME_LAUNCHED or ENDGAME_TRANSIT * * Returns TRUE if this atom is on a shuttle which is escaping or has escaped, or FALSE otherwise */ /atom/proc/on_escaped_shuttle(check_for_launch_status = ENDGAME_LAUNCHED) var/turf/current_turf = get_turf(src) if(!current_turf) return FALSE for(var/obj/docking_port/mobile/mobile_docking_port as anything in SSshuttle.mobile_docking_ports) if(mobile_docking_port.launch_status != check_for_launch_status) continue for(var/area/shuttle/shuttle_area as anything in mobile_docking_port.shuttle_areas) if(shuttle_area == current_turf.loc) return TRUE return FALSE /** * Is the atom in an away mission * * Must be in the away mission z-level to return TRUE * * Also used in gamemode code for win conditions */ /atom/proc/onAwayMission() var/turf/current_turf = get_turf(src) if(!current_turf) return FALSE if(is_away_level(current_turf.z)) return TRUE return FALSE /** * Called whenever an item is crafted, either via stack recipes or crafting recipes from the crafting menu * * By default, it just cycles through the list of movables used in the recipe and calls used_in_craft() for each of them, * then it either moves them inside the object if they're in the list of parts for the recipe * or deletes them if they're not. * The proc can be overriden by subtypes, as long as it always call parent. */ /atom/proc/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ATOM_ON_CRAFT, components, current_recipe) var/list/remaining_parts = current_recipe?.parts?.Copy() var/list/parts_by_type = remaining_parts?.Copy() for(var/parttype in parts_by_type) //necessary for our is_type_in_list() call with the zebra arg set to true parts_by_type[parttype] = parttype for(var/atom/movable/movable as anything in components) // machinery or structure objects in the list are guaranteed to be used up. We only check items. movable.used_in_craft(src, current_recipe) var/matched_type = is_type_in_list(movable, parts_by_type, zebra = TRUE) if(!matched_type) continue if(isliving(movable.loc) && isitem(movable)) var/mob/living/living = movable.loc living.transferItemToLoc(movable, src) else movable.forceMove(src) if(matched_type) remaining_parts[matched_type] -= 1 if(remaining_parts[matched_type] <= 0) remaining_parts -= matched_type ///Take air from the passed in gas mixture datum /atom/proc/assume_air(datum/gas_mixture/giver) return null ///Remove air from this atom /atom/proc/remove_air(amount) return null ///Return the current air environment in this atom /atom/proc/return_air() if(loc) return loc.return_air() else return null ///Return the air if we can analyze it /atom/proc/return_analyzable_air() return null /atom/proc/Bumped(atom/movable/bumped_atom) set waitfor = FALSE SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, bumped_atom) /// 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() return reagents && (reagents.flags & (INJECTABLE | REFILLABLE)) /// Can we draw from this atom with an injectable atom /atom/proc/is_drawable() return reagents && (reagents.flags & (DRAWABLE | DRAINABLE)) /// Can this atoms reagents be refilled /atom/proc/is_refillable() return reagents && (reagents.flags & REFILLABLE) /// Is this atom drainable of reagents /atom/proc/is_drainable() return reagents && (reagents.flags & DRAINABLE) /** Handles exposing this atom to a list of reagents. * * Sends COMSIG_ATOM_EXPOSE_REAGENTS * Calls expose_atom() for every reagent in the reagent list. * * Arguments: * - [reagents][/list]: The list of reagents the atom is being exposed to. * - [source][/datum/reagents]: The reagent holder the reagents are being sourced from. * - methods: How the atom is being exposed to the reagents. Bitflags. * - show_message: Whether to display anything to mobs when they are exposed. */ /atom/proc/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, show_message=TRUE) . = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, methods, show_message) if(. & COMPONENT_NO_EXPOSE_REAGENTS) return for(var/datum/reagent/current_reagent as anything in reagents) . |= current_reagent.expose_atom(src, reagents[current_reagent], methods) /// Are you allowed to drop this atom /atom/proc/AllowDrop() return FALSE ///Is this atom within 1 tile of another atom /atom/proc/HasProximity(atom/movable/proximity_check_mob as mob|obj) return /// Sets the wire datum of an atom /atom/proc/set_wires(datum/wires/new_wires) wires = new_wires ///Return true if we're inside the passed in atom /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 /** * Checks the atom's loc and calls update_held_items on it if it is a mob. * * This should only be used in situations when you are unable to use /datum/element/update_icon_updates_onmob for whatever reason. * Check code/datums/elements/update_icon_updates_onmob.dm before using this. Adding that to the atom and calling update_appearance will work for most cases. * * Arguments: * * mob/target - The mob to update the icons of. Optional argument, use if the atom's loc is not the mob you want to update. */ /atom/proc/update_inhand_icon(mob/target = loc) SHOULD_CALL_PARENT(TRUE) if(!istype(target)) return target.update_held_items() SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_INHAND_ICON, target) /** * An atom we are buckled or is contained within us has tried to move * * Default behaviour is to send a warning that the user can't move while buckled as long * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks) */ /atom/proc/relaymove(mob/living/user, direction) if(SEND_SIGNAL(src, COMSIG_ATOM_RELAYMOVE, user, direction) & COMSIG_BLOCK_RELAYMOVE) return if(buckle_message_cooldown <= world.time) buckle_message_cooldown = world.time + 25 balloon_alert(user, "can't move while buckled!") return /** * 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) ///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() var/datum/blood_type/blood_type = get_bloodtype() if (!blood_type) return return list(blood_type.dna_string = blood_type) ///Get the mobs dna list /mob/living/carbon/get_blood_dna_list() var/datum/blood_type/blood_type = get_bloodtype() if (!blood_type) return if (dna?.unique_enzymes) return list(dna.unique_enzymes = blood_type) return list(blood_type.dna_string = blood_type) /mob/living/silicon/get_blood_dna_list() return ///Is this atom in space /atom/proc/isinspace() if(isspaceturf(get_turf(src))) return TRUE else return FALSE /** * If someone's trying to dump items onto our atom, where should they be dumped to? * * Return a loc to place objects, or null to stop dumping. */ /atom/proc/get_dumping_location() return null /** * the vision impairment to give to the mob whose perspective is set to that atom * * (e.g. an unfocused camera giving you an impaired vision when looking through it) */ /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) return /** * Hook for running code when a dir change occurs * * Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc) */ /atom/proc/setDir(newdir) SHOULD_CALL_PARENT(TRUE) if (SEND_SIGNAL(src, COMSIG_ATOM_PRE_DIR_CHANGE, dir, newdir) & COMPONENT_ATOM_BLOCK_DIR_CHANGE) newdir = dir return SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) var/oldDir = dir dir = newdir SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, oldDir, newdir) if(smoothing_flags & SMOOTH_BORDER_OBJECT) QUEUE_SMOOTH_NEIGHBORS(src) /** * Wash this atom * * This will clean it off any temporary stuff like blood. Override this in your item to add custom cleaning behavior. * Returns true if any washing was necessary and thus performed * Arguments: * * clean_types: any of the CLEAN_ constants * Returns: A bitflag if it successfully cleaned something: e.g. COMPONENT_CLEANED, or NONE if not. COMPONENT_CLEANED_GAIN_XP being flipped on signals whether the cleaning should yield cleaning xp. */ /atom/proc/wash(clean_types) SHOULD_CALL_PARENT(TRUE) . = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, clean_types) if(.) return // Basically "if has washable coloration" if(length(atom_colours) >= WASHABLE_COLOUR_PRIORITY && atom_colours[WASHABLE_COLOUR_PRIORITY]) remove_atom_colour(WASHABLE_COLOUR_PRIORITY) return COMPONENT_CLEANED|COMPONENT_CLEANED_GAIN_XP return NONE ///Where atoms should drop if taken from this atom /atom/proc/drop_location() var/atom/location = loc if(!location) return null return location.AllowDrop() ? location : location.drop_location() /** * An atom has entered this atom's contents * * Default behaviour is to send the [COMSIG_ATOM_ENTERED] */ /atom/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, arrived, old_loc, old_locs) SEND_SIGNAL(arrived, COMSIG_ATOM_ENTERING, src, old_loc, old_locs) /** * An atom is attempting to exit this atom's contents * * Default behaviour is to send the [COMSIG_ATOM_EXIT] */ /atom/Exit(atom/movable/leaving, direction) // Don't call `..()` here, otherwise `Uncross()` gets called. // See the doc comment on `Uncross()` to learn why this is bad. if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT) return FALSE return TRUE /** * An atom has exited this atom's contents * * Default behaviour is to send the [COMSIG_ATOM_EXITED] */ /atom/Exited(atom/movable/gone, direction) SEND_SIGNAL(src, COMSIG_ATOM_EXITED, gone, direction) SEND_SIGNAL(gone, COMSIG_ATOM_EXITING, src, direction) ///Return atom temperature /atom/proc/return_temperature() return /atom/proc/process_recipes(mob/living/user, obj/item/processed_object, list/processing_recipes) //Only one recipe? use the first if(processing_recipes.len == 1) StartProcessingAtom(user, processed_object, processing_recipes[1]) return //Otherwise, select one with a radial ShowProcessingGui(user, processed_object, processing_recipes) ///Creates the radial and processes the selected option /atom/proc/ShowProcessingGui(mob/living/user, obj/item/processed_object, list/possible_options) var/list/choices_to_options = list() //Dict of object name | dict of object processing settings var/list/choices = list() for(var/list/current_option as anything in possible_options) var/atom/current_option_type = current_option[TOOL_PROCESSING_RESULT] choices_to_options[initial(current_option_type.name)] = current_option var/image/option_image = image(icon = initial(current_option_type.icon), icon_state = initial(current_option_type.icon_state)) choices += list("[initial(current_option_type.name)]" = option_image) var/pick = show_radial_menu(user, src, choices, radius = 36, require_near = TRUE) if(!pick) return StartProcessingAtom(user, processed_object, choices_to_options[pick]) /atom/proc/StartProcessingAtom(mob/living/user, obj/item/process_item, list/chosen_option) var/processing_time = chosen_option[TOOL_PROCESSING_TIME] var/sound_to_play = chosen_option[TOOL_PROCESSING_SOUND] to_chat(user, span_notice("You start working on [src].")) if(sound_to_play) playsound(src, sound_to_play, 50, TRUE) if(!process_item.use_tool(src, user, processing_time, volume=50)) return var/atom/atom_to_create = chosen_option[TOOL_PROCESSING_RESULT] var/list/atom/created_atoms = list() var/amount_to_create = chosen_option[TOOL_PROCESSING_AMOUNT] for(var/i = 1 to amount_to_create) var/atom/created_atom = new atom_to_create(drop_location()) created_atom.OnCreatedFromProcessing(user, process_item, chosen_option, src) if(custom_materials) created_atom.set_custom_materials(custom_materials, 1 / amount_to_create) created_atom.pixel_x = pixel_x created_atom.pixel_y = pixel_y if(i > 1) created_atom.pixel_x += rand(-8,8) created_atom.pixel_y += rand(-8,8) created_atoms.Add(created_atom) to_chat(user, span_notice("You manage to create [amount_to_create] [initial(atom_to_create.gender) == PLURAL ? "[initial(atom_to_create.name)]" : "[initial(atom_to_create.name)][plural_s(initial(atom_to_create.name))]"] from [src].")) SEND_SIGNAL(src, COMSIG_ATOM_PROCESSED, user, process_item, created_atoms) UsedforProcessing(user, process_item, chosen_option, created_atoms) /atom/proc/UsedforProcessing(mob/living/user, obj/item/used_item, list/chosen_option, list/created_atoms) qdel(src) return /atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/work_tool, list/chosen_option, atom/original_atom) SHOULD_CALL_PARENT(TRUE) if(HAS_TRAIT(original_atom, TRAIT_FOOD_SILVER)) ADD_TRAIT(src, TRAIT_FOOD_SILVER, INNATE_TRAIT) // stinky food always stinky SEND_SIGNAL(src, COMSIG_ATOM_CREATEDBY_PROCESSING, original_atom, chosen_option) if(user.mind) ADD_TRAIT(src, TRAIT_FOOD_CHEF_MADE, REF(user.mind)) ///Connect this atom to a shuttle /atom/proc/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) return /atom/proc/intercept_zImpact(list/falling_movables, levels = 1) SHOULD_CALL_PARENT(TRUE) . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, falling_movables, levels) ///Setter for the `density` variable to append behavior related to its changing. /atom/proc/set_density(new_value) SHOULD_CALL_PARENT(TRUE) if(density == new_value) return . = density density = new_value SEND_SIGNAL(src, COMSIG_ATOM_DENSITY_CHANGED) ///Setter for the `base_pixel_x` variable to append behavior related to its changing. /atom/proc/set_base_pixel_x(new_value) if(base_pixel_x == new_value) return . = base_pixel_x base_pixel_x = new_value pixel_x = pixel_x + base_pixel_x - . ///Setter for the `base_pixel_y` variable to append behavior related to its changing. /atom/proc/set_base_pixel_y(new_value) if(base_pixel_y == new_value) return . = base_pixel_y base_pixel_y = new_value pixel_y = pixel_y + base_pixel_y - . // Not a valid operation, turfs and movables handle block differently /atom/proc/set_explosion_block(explosion_block) return /** * Returns true if this atom has gravity for the passed in turf * * Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with * the forced gravity var. * * micro-optimized to hell because this proc is very hot, being called several times per movement every movement. * * HEY JACKASS, LISTEN * IF YOU ADD SOMETHING TO THIS PROC, MAKE SURE /mob/living ACCOUNTS FOR IT * Living mobs treat gravity in an event based manner. We've decomposed this proc into different checks * for them to use. If you add more to it, make sure you do that, or things will behave strangely * * Gravity situations: * * No gravity if you're not in a turf * * No gravity if this atom is in is a space turf * * No gravity if the area has NO_GRAVITY flag (space, ordnance bomb site, nearstation, solars) * * Gravity if the area it's in always has gravity * * Gravity if there's a gravity generator on the z level * * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY * * otherwise no gravity */ /atom/proc/has_gravity(turf/gravity_turf) if(!isturf(gravity_turf)) gravity_turf = get_turf(src) if(!gravity_turf)//no gravity in nullspace return FALSE var/list/forced_gravity = list() SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity) SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) if(length(forced_gravity)) var/positive_grav = max(forced_gravity) var/negative_grav = min(min(forced_gravity), 0) //negative grav needs to be below or equal to 0 //our gravity is sum of the most massive positive and negative numbers returned by the signal //so that adding two forced_gravity elements with an effect size of 1 each doesnt add to 2 gravity //but negative force gravity effects can cancel out positive ones return (positive_grav + negative_grav) var/area/turf_area = gravity_turf.loc return (!gravity_turf.force_no_gravity && !(turf_area.area_flags & NO_GRAVITY)) && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.default_gravity) /** * Used to set something as 'open' if it's being used as a supplypod * * Override this if you want an atom to be usable as a supplypod. */ /atom/proc/setOpened() return /** * Used to set something as 'closed' if it's being used as a supplypod * * Override this if you want an atom to be usable as a supplypod. */ /atom/proc/setClosed() return ///Called after the atom is 'tamed' for type-specific operations, Usually called by the tameable component but also other things. /atom/proc/tamed(mob/living/tamer, obj/item/food) return /** * Used to attempt to charge an object with a payment component. * * Use this if an atom needs to attempt to charge another atom. */ /atom/proc/attempt_charge(atom/sender, atom/target, extra_fees = 0) return SEND_SIGNAL(sender, COMSIG_OBJ_ATTEMPT_CHARGE, target, extra_fees) ///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 var/client/usr_client = usr.client var/list/paramslist = list() if(href_list["statpanel_item_click"]) 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 /atom/MouseEntered(location, control, params) SSmouse_entered.hovers[usr.client] = src /// Fired whenever this atom is the most recent to be hovered over in the tick. /// Preferred over MouseEntered if you do not need information such as the position of the mouse. /// Especially because this is deferred over a tick, do not trust that `client` is not null. /atom/proc/on_mouse_enter(client/client) SHOULD_NOT_SLEEP(TRUE) var/mob/user = client?.mob if (isnull(user)) return SEND_SIGNAL(user, COMSIG_ATOM_MOUSE_ENTERED, src) // Screentips var/datum/hud/active_hud = user.hud_used if(!active_hud) return var/screentips_enabled = active_hud.screentips_enabled if(screentips_enabled == SCREENTIP_PREFERENCE_DISABLED || flags_1 & NO_SCREENTIPS_1) active_hud.screentip_text.maptext = "" return var/lmb_rmb_line = "" var/ctrl_lmb_ctrl_rmb_line = "" var/alt_lmb_alt_rmb_line = "" var/shift_lmb_ctrl_shift_lmb_line = "" var/extra_lines = 0 var/extra_context = "" var/used_name = name if(isliving(user) || isovermind(user) || iscameramob(user) || (ghost_screentips && isobserver(user))) var/obj/item/held_item = user.get_active_held_item() if (user.mob_flags & MOB_HAS_SCREENTIPS_NAME_OVERRIDE) var/list/returned_name = list(used_name) var/name_override_returns = SEND_SIGNAL(user, COMSIG_MOB_REQUESTING_SCREENTIP_NAME_FROM_USER, returned_name, held_item, src) if (name_override_returns & SCREENTIP_NAME_SET) used_name = returned_name[1] if (flags_1 & HAS_CONTEXTUAL_SCREENTIPS_1 || held_item?.item_flags & ITEM_HAS_CONTEXTUAL_SCREENTIPS) var/list/context = list() var/contextual_screentip_returns = \ SEND_SIGNAL(src, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, context, held_item, user) \ | (held_item && SEND_SIGNAL(held_item, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, context, src, user)) if (contextual_screentip_returns & CONTEXTUAL_SCREENTIP_SET) var/screentip_images = active_hud.screentip_images // LMB and RMB on one line... var/lmb_text = build_context(context, SCREENTIP_CONTEXT_LMB, screentip_images) var/rmb_text = build_context(context, SCREENTIP_CONTEXT_RMB, screentip_images) if (lmb_text != "") lmb_rmb_line = lmb_text if (rmb_text != "") lmb_rmb_line += " | [rmb_text]" else if (rmb_text != "") lmb_rmb_line = rmb_text // Ctrl-LMB, Ctrl-RMB on one line... if (lmb_rmb_line != "") lmb_rmb_line += "
" extra_lines++ if (SCREENTIP_CONTEXT_CTRL_LMB in context) ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_LMB, screentip_images) if (SCREENTIP_CONTEXT_CTRL_RMB in context) if (ctrl_lmb_ctrl_rmb_line != "") ctrl_lmb_ctrl_rmb_line += " | " ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_RMB, screentip_images) // Alt-LMB, Alt-RMB on one line... if (ctrl_lmb_ctrl_rmb_line != "") ctrl_lmb_ctrl_rmb_line += "
" extra_lines++ if (SCREENTIP_CONTEXT_ALT_LMB in context) alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_LMB, screentip_images) if (SCREENTIP_CONTEXT_ALT_RMB in context) if (alt_lmb_alt_rmb_line != "") alt_lmb_alt_rmb_line += " | " alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_RMB, screentip_images) // Shift-LMB, Ctrl-Shift-LMB on one line... if (alt_lmb_alt_rmb_line != "") alt_lmb_alt_rmb_line += "
" extra_lines++ if (SCREENTIP_CONTEXT_SHIFT_LMB in context) shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_SHIFT_LMB, screentip_images) if (SCREENTIP_CONTEXT_CTRL_SHIFT_LMB in context) if (shift_lmb_ctrl_shift_lmb_line != "") shift_lmb_ctrl_shift_lmb_line += " | " shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_SHIFT_LMB, screentip_images) if (shift_lmb_ctrl_shift_lmb_line != "") extra_lines++ if(extra_lines) extra_context = "
[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line]" var/new_maptext if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "") new_maptext = "" else //We inline a MAPTEXT() here, because there's no good way to statically add to a string like this new_maptext = "[used_name][extra_context]" if (length(used_name) * 10 > active_hud.screentip_text.maptext_width) INVOKE_ASYNC(src, PROC_REF(set_hover_maptext), client, active_hud, new_maptext) return active_hud.screentip_text.maptext = new_maptext active_hud.screentip_text.maptext_y = 10 - (extra_lines > 0 ? 11 + 9 * (extra_lines - 1): 0) /atom/proc/set_hover_maptext(client/client, datum/hud/active_hud, new_maptext) var/map_height WXH_TO_HEIGHT(client.MeasureText(new_maptext, null, active_hud.screentip_text.maptext_width), map_height) active_hud.screentip_text.maptext = new_maptext active_hud.screentip_text.maptext_y = 26 - map_height /** * 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 CanAStarPass will always return true if density is FALSE * If this is NOT you, ensure you edit your can_astar_pass variable. Check __DEFINES/path.dm **/ /atom/proc/CanAStarPass(to_dir, datum/can_pass_info/pass_info) if(pass_info.pass_flags & pass_flags_self) return TRUE . = !density