diff --git a/code/__HELPERS/animations.dm b/code/__HELPERS/animations.dm
deleted file mode 100644
index cae8d3a8f52..00000000000
--- a/code/__HELPERS/animations.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * Causes the passed atom / image to appear floating,
- * playing a simple animation where they move up and down by 2 pixels (looping)
- *
- * In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
- * This is just so you can apply the animation to things which can be animated but are not movables (like images)
- */
-#define DO_FLOATING_ANIM(target) \
- animate(target, pixel_y = 2, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE); \
- animate(pixel_y = -2, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
-
-/**
- * Stops the passed atom / image from appearing floating
- * (Living mobs also have a 'body_position_pixel_y_offset' variable that has to be taken into account here)
- *
- * In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
- * This is just so you can apply the animation to things which can be animated but are not movables (like images)
- */
-#define STOP_FLOATING_ANIM(target) \
- var/final_pixel_y = 0; \
- if(ismovable(target)) { \
- var/atom/movable/movable_target = target; \
- final_pixel_y = movable_target.base_pixel_y; \
- }; \
- if(isliving(target)) { \
- var/mob/living/living_target = target; \
- final_pixel_y += living_target.body_position_pixel_y_offset; \
- }; \
- animate(target, pixel_y = final_pixel_y, time = 1 SECONDS)
-
-/// The duration of the animate call in mob/living/update_transform
-#define UPDATE_TRANSFORM_ANIMATION_TIME (0.2 SECONDS)
-
-///Animates source spinning around itself. For docmentation on the args, check atom/proc/SpinAnimation()
-/atom/proc/do_spin_animation(speed = 1 SECONDS, loops = -1, segments = 3, angle = 120, parallel = TRUE)
- var/list/matrices = list()
- for(var/i in 1 to segments-1)
- var/matrix/segment_matrix = matrix(transform)
- segment_matrix.Turn(angle*i)
- matrices += segment_matrix
- var/matrix/last = matrix(transform)
- matrices += last
-
- speed /= segments
-
- if(parallel)
- animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
- else
- animate(src, transform = matrices[1], time = speed, loops)
- for(var/i in 2 to segments) //2 because 1 is covered above
- animate(transform = matrices[i], time = speed)
- //doesn't have an object argument because this is "Stacking" with the animate call above
- //3 billion% intentional
-
-/// Similar to shake but more spasm-y and jerk-y
-/atom/proc/spasm_animation(loops = -1)
- var/list/transforms = list(
- matrix(transform).Translate(-1, 0),
- matrix(transform).Translate(0, 1),
- matrix(transform).Translate(1, 0),
- matrix(transform).Translate(0, -1),
- )
-
- animate(src, transform = transforms[1], time = 0.2, loop = loops)
- animate(transform = transforms[2], time = 0.1)
- animate(transform = transforms[3], time = 0.2)
- animate(transform = transforms[4], time = 0.3)
diff --git a/code/__HELPERS/visual_effects.dm b/code/__HELPERS/visual_effects.dm
new file mode 100644
index 00000000000..b7855ade45f
--- /dev/null
+++ b/code/__HELPERS/visual_effects.dm
@@ -0,0 +1,125 @@
+/**
+ * Causes the passed atom / image to appear floating,
+ * playing a simple animation where they move up and down by 2 pixels (looping)
+ *
+ * In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
+ * This is just so you can apply the animation to things which can be animated but are not movables (like images)
+ */
+#define DO_FLOATING_ANIM(target) \
+ animate(target, pixel_y = 2, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE); \
+ animate(pixel_y = -2, time = 1 SECONDS, flags = ANIMATION_RELATIVE)
+
+/**
+ * Stops the passed atom / image from appearing floating
+ * (Living mobs also have a 'body_position_pixel_y_offset' variable that has to be taken into account here)
+ *
+ * In most cases you should NOT call this manually, instead use [/datum/element/movetype_handler]!
+ * This is just so you can apply the animation to things which can be animated but are not movables (like images)
+ */
+#define STOP_FLOATING_ANIM(target) \
+ var/final_pixel_y = 0; \
+ if(ismovable(target)) { \
+ var/atom/movable/movable_target = target; \
+ final_pixel_y = movable_target.base_pixel_y; \
+ }; \
+ if(isliving(target)) { \
+ var/mob/living/living_target = target; \
+ final_pixel_y += living_target.body_position_pixel_y_offset; \
+ }; \
+ animate(target, pixel_y = final_pixel_y, time = 1 SECONDS)
+
+/// The duration of the animate call in mob/living/update_transform
+#define UPDATE_TRANSFORM_ANIMATION_TIME (0.2 SECONDS)
+
+///Animates source spinning around itself. For docmentation on the args, check atom/proc/SpinAnimation()
+/atom/proc/do_spin_animation(speed = 1 SECONDS, loops = -1, segments = 3, angle = 120, parallel = TRUE)
+ var/list/matrices = list()
+ for(var/i in 1 to segments-1)
+ var/matrix/segment_matrix = matrix(transform)
+ segment_matrix.Turn(angle*i)
+ matrices += segment_matrix
+ var/matrix/last = matrix(transform)
+ matrices += last
+
+ speed /= segments
+
+ if(parallel)
+ animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
+ else
+ animate(src, transform = matrices[1], time = speed, loops)
+ for(var/i in 2 to segments) //2 because 1 is covered above
+ animate(transform = matrices[i], time = speed)
+ //doesn't have an object argument because this is "Stacking" with the animate call above
+ //3 billion% intentional
+
+/// Similar to shake but more spasm-y and jerk-y
+/atom/proc/spasm_animation(loops = -1)
+ var/list/transforms = list(
+ matrix(transform).Translate(-1, 0),
+ matrix(transform).Translate(0, 1),
+ matrix(transform).Translate(1, 0),
+ matrix(transform).Translate(0, -1),
+ )
+
+ animate(src, transform = transforms[1], time = 0.2, loop = loops)
+ animate(transform = transforms[2], time = 0.1)
+ animate(transform = transforms[3], time = 0.2)
+ animate(transform = transforms[4], time = 0.3)
+
+/**
+ * Proc called when you want the atom to spin around the center of its icon (or where it would be if its transform var is translated)
+ * By default, it makes the atom spin forever and ever at a speed of 60 rpm.
+ *
+ * Arguments:
+ * * speed: how much it takes for the atom to complete one 360° rotation
+ * * loops: how many times do we want the atom to rotate
+ * * clockwise: whether the atom ought to spin clockwise or counter-clockwise
+ * * segments: in how many animate calls the rotation is split. Probably unnecessary, but you shouldn't set it lower than 3 anyway.
+ * * parallel: whether the animation calls have the ANIMATION_PARALLEL flag, necessary for it to run alongside concurrent animations.
+ */
+/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = TRUE, segments = 3, parallel = TRUE)
+ if(!segments)
+ return
+ var/segment = 360/segments
+ if(!clockwise)
+ segment = -segment
+ SEND_SIGNAL(src, COMSIG_ATOM_SPIN_ANIMATION, speed, loops, segments, segment)
+ do_spin_animation(speed, loops, segments, segment, parallel)
+
+/// Makes this atom look like a "hologram"
+/// So transparent, blue, with a scanline and an emissive glow
+/// This is acomplished using a combination of filters and render steps/overlays
+/// The degree of the opacity is optional, based off the opacity arg (0 -> 1)
+/atom/proc/makeHologram(opacity = 0.5)
+ // First, we'll make things blue (roughly) and sorta transparent
+ add_filter("HOLO: Color and Transparent", 1, color_matrix_filter(rgb(125,180,225, opacity * 255)))
+ // Now we're gonna do a scanline effect
+ // Gonna take this atom and give it a render target, then use it as a source for a filter
+ // (We use an atom because it seems as if setting render_target on an MA is just invalid. I hate this engine)
+ var/atom/movable/scanline = new(null)
+ scanline.icon = 'icons/effects/effects.dmi'
+ scanline.icon_state = "scanline"
+ scanline.appearance_flags |= RESET_TRANSFORM
+ // * so it doesn't render
+ var/static/uid_scan = 0
+ scanline.render_target = "*HoloScanline [uid_scan]"
+ uid_scan++
+ // Now we add it as a filter, and overlay the appearance so the render source is always around
+ add_filter("HOLO: Scanline", 2, alpha_mask_filter(render_source = scanline.render_target))
+ add_overlay(scanline)
+ qdel(scanline)
+ // Annd let's make the sucker emissive, so it glows in the dark
+ if(!render_target)
+ var/static/uid = 0
+ render_target = "HOLOGRAM [uid]"
+ uid++
+ // I'm using static here to reduce the overhead, it does mean we need to do plane stuff manually tho
+ var/static/atom/movable/render_step/emissive/glow = new(null)
+ glow.render_source = render_target
+ SET_PLANE_EXPLICIT(glow, initial(glow.plane), src)
+ // We're creating a render step that copies ourselves, and draws it to the emissive plane
+ // Then we overlay it, and release "ownership" back to this proc, since we get to keep the appearance it generates
+ // We can't just use an MA from the start cause render_source setting starts going fuckey REALLY quick
+ var/mutable_appearance/glow_appearance = new(glow)
+ add_overlay(glow_appearance)
+ LAZYADD(update_overlays_on_z, glow_appearance)
diff --git a/code/game/atom/README.md b/code/game/atom/README.md
new file mode 100644
index 00000000000..3ce4f9b0cf9
--- /dev/null
+++ b/code/game/atom/README.md
@@ -0,0 +1,3 @@
+# Core /atom Systems
+
+This is a folder of basic systems that apply to all /atom types, split into relevant files. Keep the files named fittingly and don't put anything in `_atom.dm` if you can avoid it.
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
new file mode 100644
index 00000000000..e1185949dc8
--- /dev/null
+++ b/code/game/atom/_atom.dm
@@ -0,0 +1,959 @@
+/**
+ * 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
+ layer = 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 = STATIC_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 //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.
+ ///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. 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
+
+ /// How this atom should react to having its astar blocking checked
+ var/can_astar_pass = CANASTARPASS_DENSITY
+
+/**
+ * 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)
+
+ 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)
+
+ 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(current_turf in shuttle_area.get_contained_turfs())
+ 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
+
+/**
+ * Ensure a list of atoms/reagents exists inside this atom
+ *
+ * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder
+ * creating the reagent holder if it exists.
+ *
+ * If the part is a moveable atom and the previous location of the item was a mob/living,
+ * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part
+ * to this atom
+ *
+ * Otherwise it simply forceMoves the atom into this atom
+ */
+/atom/proc/CheckParts(list/parts_list, datum/crafting_recipe/current_recipe)
+ SEND_SIGNAL(src, COMSIG_ATOM_CHECKPARTS, parts_list, current_recipe)
+ if(!parts_list)
+ return
+ for(var/part in parts_list)
+ if(istype(part, /datum/reagent))
+ if(!reagents)
+ reagents = new()
+ reagents.reagent_list.Add(part)
+ reagents.conditional_update()
+ else if(ismovable(part))
+ var/atom/movable/object = part
+ if(isliving(object.loc))
+ var/mob/living/living = object.loc
+ living.transferItemToLoc(object, src)
+ else
+ object.forceMove(src)
+ SEND_SIGNAL(object, COMSIG_ATOM_USED_IN_CRAFT, src)
+ parts_list.Cut()
+
+///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
+
+///Check if this atoms eye is still alive (probably)
+/atom/proc/check_eye(mob/user)
+ SIGNAL_HANDLER
+ return
+
+/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(mob/user, allowmobs = TRUE)
+ return reagents && (reagents.flags & (INJECTABLE | REFILLABLE))
+
+/// Can we draw from this atom with an injectable atom
+/atom/proc/is_drawable(mob/user, allowmobs = TRUE)
+ 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.
+ * - volume_modifier: Volume multiplier.
+ * - show_message: Whether to display anything to mobs when they are exposed.
+ */
+/atom/proc/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
+ if(. & COMPONENT_NO_EXPOSE_REAGENTS)
+ return
+
+ SEND_SIGNAL(source, COMSIG_REAGENTS_EXPOSE_ATOM, src, reagents, methods, volume_modifier, show_message)
+ for(var/datum/reagent/current_reagent as anything in reagents)
+ . |= current_reagent.expose_atom(src, reagents[current_reagent])
+ SEND_SIGNAL(src, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
+
+/// 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()
+ if(get_blood_id() != /datum/reagent/blood)
+ return
+ return list("ANIMAL DNA" = "Y-")
+
+///Get the mobs dna list
+/mob/living/carbon/get_blood_dna_list()
+ if(get_blood_id() != /datum/reagent/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*")
+
+/mob/living/silicon/get_blood_dna_list()
+ return
+
+///to add a mob's dna info into an object's blood_dna list.
+/atom/proc/transfer_mob_blood_dna(mob/living/injected_mob)
+ // Returns 0 if we have that blood already
+ var/new_blood_dna = injected_mob.get_blood_dna_list()
+ if(!new_blood_dna)
+ return FALSE
+ var/old_length = GET_ATOM_BLOOD_DNA_LENGTH(src)
+ add_blood_DNA(new_blood_dna)
+ if(GET_ATOM_BLOOD_DNA_LENGTH(src) == old_length)
+ return FALSE
+ return TRUE
+
+///to add blood from a mob onto something, and transfer their dna info
+/atom/proc/add_mob_blood(mob/living/injected_mob)
+ var/list/blood_dna = injected_mob.get_blood_dna_list()
+ if(!blood_dna)
+ return FALSE
+ return add_blood_DNA(blood_dna)
+
+///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)
+ dir = newdir
+ SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, dir, 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
+ */
+/atom/proc/wash(clean_types)
+ SHOULD_CALL_PARENT(TRUE)
+ if(SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, clean_types) & COMPONENT_CLEANED)
+ return TRUE
+
+ // Basically "if has washable coloration"
+ if(length(atom_colours) >= WASHABLE_COLOUR_PRIORITY && atom_colours[WASHABLE_COLOUR_PRIORITY])
+ remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ return TRUE
+ return FALSE
+
+///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)
+
+///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]
+ to_chat(user, span_notice("You start working on [src]."))
+ if(process_item.use_tool(src, user, processing_time, volume=50))
+ 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())
+ 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_atom.OnCreatedFromProcessing(user, process_item, chosen_option, src)
+ 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)
+ return
+
+/atom/proc/UsedforProcessing(mob/living/user, obj/item/used_item, list/chosen_option)
+ qdel(src)
+ return
+
+/atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/work_tool, list/chosen_option, atom/original_atom)
+ SHOULD_CALL_PARENT(TRUE)
+
+ 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
+
+
+///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
+ * * 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 && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.has_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
+
+/**
+ * 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
+ ///SKYRAT EDIT ADDITION BEGIN
+ // Face directions on combat mode. No procs, no typechecks, just a var for speed
+ if(user.face_mouse)
+ user.face_atom(src)
+ ///SKYRAT EDIT ADDITION END
+
+ // 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
+
+ active_hud.screentip_text.maptext_y = 10 // 10px lines us up with the action buttons top left corner
+ 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 = ""
+
+ if (isliving(user) || isovermind(user) || isaicamera(user))
+ var/obj/item/held_item = user.get_active_held_item()
+
+ 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]"
+ //first extra line pushes atom name line up 11px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
+ active_hud.screentip_text.maptext_y = -1 + (extra_lines - 1) * -9
+
+ if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
+ active_hud.screentip_text.maptext = ""
+ else
+ //We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
+ active_hud.screentip_text.maptext = "[name][extra_context]"
+
+/**
+ * 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
diff --git a/code/game/alternate_appearance.dm b/code/game/atom/alternate_appearance.dm
similarity index 99%
rename from code/game/alternate_appearance.dm
rename to code/game/atom/alternate_appearance.dm
index 9f3da121b12..86732dd9ed4 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/atom/alternate_appearance.dm
@@ -1,5 +1,8 @@
GLOBAL_LIST_EMPTY(active_alternate_appearances)
+/atom
+ var/list/alternate_appearances
+
/atom/proc/remove_alt_appearance(key)
if(alternate_appearances)
for(var/K in alternate_appearances)
diff --git a/code/game/atom/atom_act.dm b/code/game/atom/atom_act.dm
new file mode 100644
index 00000000000..4c4ec04d667
--- /dev/null
+++ b/code/game/atom/atom_act.dm
@@ -0,0 +1,226 @@
+/*
+ * +++++++++++++++++++++++++++++++++++++++++ ABOUT THIS FILE +++++++++++++++++++++++++++++++++++++++++++++
+ * Not everything here necessarily has the name pattern of [x]_act()
+ * This is a file for various atom procs that simply get called when something is happening to that atom.
+ * If you're adding something here, you likely want a signal and SHOULD_CALL_PARENT(TRUE)
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+/**
+ * Respond to fire being used on our atom
+ *
+ * Default behaviour is to send [COMSIG_ATOM_FIRE_ACT] and return
+ */
+/atom/proc/fire_act(exposed_temperature, exposed_volume)
+ SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
+ return FALSE
+
+/**
+ * Sends [COMSIG_ATOM_EXTINGUISH] signal, which properly removes burning component if it is present.
+ *
+ * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return
+ */
+/atom/proc/extinguish()
+ SHOULD_CALL_PARENT(TRUE)
+ return SEND_SIGNAL(src, COMSIG_ATOM_EXTINGUISH)
+
+/**
+ * React to being hit by an explosion
+ *
+ * Should be called through the [EX_ACT] wrapper macro.
+ * The wrapper takes care of the [COMSIG_ATOM_EX_ACT] signal.
+ * as well as calling [/atom/proc/contents_explosion].
+ *
+ * Returns TRUE by default, and behavior should be implemented on children procs on a per-atom basis. Should only return FALSE if we resist the explosion for any reason.
+ * We assume that the default is TRUE because all atoms should be considered destructible in some manner unless they explicitly opt out (in our current framework).
+ * However, the return value itself doesn't have any external consumers, it's only so children procs can listen to the value from their parent procs (due to the nature of the [EX_ACT] macro).
+ * Thus, the return value only matters on overrides of this proc, and the only thing that truly matters is the code that is executed (applying damage, calling damage procs, etc.)
+ *
+ */
+/atom/proc/ex_act(severity, target)
+ set waitfor = FALSE
+ return TRUE
+
+/// Handle what happens when your contents are exploded by a bomb
+/atom/proc/contents_explosion(severity, target)
+ return //For handling the effects of explosions on contents that would not normally be effected
+
+/**
+ * React to a hit by a blob objecd
+ *
+ * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal
+ */
+/atom/proc/blob_act(obj/structure/blob/attacking_blob)
+ var/blob_act_result = SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, attacking_blob)
+ if (blob_act_result & COMPONENT_CANCEL_BLOB_ACT)
+ return FALSE
+ return TRUE
+
+/**
+ * React to an EMP of the given severity
+ *
+ * Default behaviour is to send the [COMSIG_ATOM_PRE_EMP_ACT] and [COMSIG_ATOM_EMP_ACT] signal
+ *
+ * If the pre-signal does not return protection, and there are attached wires then we call
+ * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires
+ *
+ * We then return the protection value
+ */
+/atom/proc/emp_act(severity)
+ var/protection = SEND_SIGNAL(src, COMSIG_ATOM_PRE_EMP_ACT, severity)
+ if(!(protection & EMP_PROTECT_WIRES) && istype(wires))
+ wires.emp_pulse()
+
+ SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity, protection)
+ return protection // Pass the protection value collected here upwards
+
+/**
+ * React to a hit by a projectile object
+ *
+ * @params
+ * * hitting_projectile - projectile
+ * * def_zone - zone hit
+ * * piercing_hit - is this hit piercing or normal?
+ */
+/atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone)
+ if(sigreturn & COMPONENT_BULLET_PIERCED)
+ return BULLET_ACT_FORCE_PIERCE
+ if(sigreturn & COMPONENT_BULLET_BLOCKED)
+ return BULLET_ACT_BLOCK
+ if(sigreturn & COMPONENT_BULLET_ACTED)
+ return BULLET_ACT_HIT
+
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone)
+ if(QDELETED(hitting_projectile)) // Signal deleted it?
+ return BULLET_ACT_BLOCK
+
+ return hitting_projectile.on_hit(
+ target = src,
+ // This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since
+ // only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot.
+ blocked = check_projectile_armor(def_zone, hitting_projectile),
+ pierce_hit = piercing_hit,
+ )
+
+/**
+ * React to being hit by a thrown object
+ *
+ * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense
+ * and under normal gravity.
+ *
+ * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is
+ * deleted shortly after hitting something (during explosions or other massive events that
+ * throw lots of items around - singularity being a notable example)
+ */
+/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)
+
+/**
+ * We have have actually hit the passed in atom
+ *
+ * Default behaviour is to move back from the item that hit us
+ */
+/atom/proc/hitby_react(atom/movable/harmed_atom)
+ if(harmed_atom && isturf(harmed_atom.loc))
+ step(harmed_atom, REVERSE_DIR(harmed_atom.dir))
+
+///Handle the atom being slipped over
+/atom/proc/handle_slip(mob/living/carbon/slipped_carbon, knockdown_amount, obj/slipping_object, lube, paralyze, force_drop)
+ return
+
+///Used for making a sound when a mob involuntarily falls into the ground.
+/atom/proc/handle_fall(mob/faller)
+ return
+
+///Respond to the singularity eating this atom
+/atom/proc/singularity_act()
+ return
+
+/**
+ * Respond to the singularity pulling on us
+ *
+ * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return
+ */
+/atom/proc/singularity_pull(obj/singularity/singularity, current_size)
+ SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, singularity, 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)
+ return FALSE
+
+/**
+ * Respond to an emag being used on our atom
+ *
+ * Args:
+ * * mob/user: The mob that used the emag. Nullable.
+ * * obj/item/card/emag/emag_card: The emag that was used. Nullable.
+ *
+ * Returns:
+ * TRUE if the emag had any effect, falsey otherwise.
+ */
+/atom/proc/emag_act(mob/user, obj/item/card/emag/emag_card)
+ return (SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user, emag_card))
+
+/**
+ * Respond to narsie eating our atom
+ *
+ * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return
+ */
+/atom/proc/narsie_act()
+ SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT)
+
+/**
+ * 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
+
+/**
+ * Called when the atom log's in or out
+ *
+ * Default behaviour is to call on_log on the location this atom is in
+ */
+/atom/proc/on_log(login)
+ if(loc)
+ loc.on_log(login)
+
+/**
+ * Causes effects when the atom gets hit by a rust effect from heretics
+ *
+ * Override this if you want custom behaviour in whatever gets hit by the rust
+ */
+/atom/proc/rust_heretic_act()
+ return
+
+///Called when something resists while this atom is its loc
+/atom/proc/container_resist_act(mob/living/user)
+ return
+
+/**
+ * Respond to an RCD acting on our item
+ *
+ * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE
+ */
+/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data)
+ SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, rcd_data["[RCD_DESIGN_MODE]"])
+ return FALSE
+
+///Return the values you get when an RCD eats you?
+/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ return FALSE
+
+///This atom has been hit by a hulkified mob in hulk mode (user)
+/atom/proc/attack_hulk(mob/living/carbon/human/user)
+ SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
diff --git a/code/game/atom/atom_appearance.dm b/code/game/atom/atom_appearance.dm
new file mode 100644
index 00000000000..d3e0a64d69c
--- /dev/null
+++ b/code/game/atom/atom_appearance.dm
@@ -0,0 +1,112 @@
+/atom
+ ///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
+
+/**
+ * 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)
+
+ . = NONE
+ 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)
+ SHOULD_CALL_PARENT(TRUE)
+
+ . = NONE
+ updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates)
+ if(updates & UPDATE_ICON_STATE)
+ update_icon_state()
+ . |= UPDATE_ICON_STATE
+
+ if(updates & UPDATE_OVERLAYS)
+ if(LAZYLEN(managed_vis_overlays))
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+
+ var/list/new_overlays = update_overlays(updates)
+ var/nulls = 0
+ for(var/i in 1 to length(new_overlays))
+ var/atom/maybe_not_an_atom = new_overlays[i]
+ if(isnull(maybe_not_an_atom))
+ nulls++
+ continue
+ if(istext(maybe_not_an_atom) || isicon(maybe_not_an_atom))
+ continue
+ new_overlays[i] = maybe_not_an_atom.appearance
+ if(nulls)
+ for(var/i in 1 to nulls)
+ new_overlays -= null
+
+ var/identical = FALSE
+ var/new_length = length(new_overlays)
+ if(!managed_overlays && !new_length)
+ identical = TRUE
+ else if(!islist(managed_overlays))
+ if(new_length == 1 && managed_overlays == new_overlays[1])
+ identical = TRUE
+ else if(length(managed_overlays) == new_length)
+ identical = TRUE
+ for(var/i in 1 to length(managed_overlays))
+ if(managed_overlays[i] != new_overlays[i])
+ identical = FALSE
+ break
+
+ if(!identical)
+ var/full_control = FALSE
+ if(managed_overlays)
+ full_control = length(overlays) == (islist(managed_overlays) ? length(managed_overlays) : 1)
+ if(full_control)
+ overlays = null
+ else
+ cut_overlay(managed_overlays)
+
+ switch(length(new_overlays))
+ if(0)
+ if(full_control)
+ POST_OVERLAY_CHANGE(src)
+ managed_overlays = null
+ if(1)
+ add_overlay(new_overlays)
+ managed_overlays = new_overlays[1]
+ else
+ add_overlay(new_overlays)
+ managed_overlays = new_overlays
+
+ . |= UPDATE_OVERLAYS
+
+ . |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
+
+/// Updates the icon state of the atom
+/atom/proc/update_icon_state()
+ SHOULD_CALL_PARENT(TRUE)
+ return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
+
+/// Updates the overlays of the atom
+/atom/proc/update_overlays()
+ SHOULD_CALL_PARENT(TRUE)
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
diff --git a/code/game/atom/atom_color.dm b/code/game/atom/atom_color.dm
new file mode 100644
index 00000000000..e5b52460aa0
--- /dev/null
+++ b/code/game/atom/atom_color.dm
@@ -0,0 +1,71 @@
+/*
+ Atom Colour Priority System
+ A System that gives finer control over which atom colour to colour the atom with.
+ The "highest priority" one is always displayed as opposed to the default of
+ "whichever was set last is displayed"
+*/
+
+/atom
+ /**
+ * used to store the different colors on an atom
+ *
+ * its inherent color, the colored paint applied on it, special color effect etc...
+ */
+ var/list/atom_colours
+
+///Adds an instance of colour_type to the atom's atom_colours list
+/atom/proc/add_atom_colour(coloration, colour_priority)
+ if(!atom_colours || !atom_colours.len)
+ atom_colours = list()
+ atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
+ if(!coloration)
+ return
+ if(colour_priority > atom_colours.len)
+ 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)
+ return
+ if(colour_priority > atom_colours.len)
+ 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()
+
+/**
+ * Checks if this atom has the passed color
+ * Can optionally be supplied with a range of priorities, IE only checking "washable" or above
+ */
+/atom/proc/is_atom_colour(looking_for_color, min_priority_index = 1, max_priority_index = COLOUR_PRIORITY_AMOUNT)
+ // make sure uppertext hex strings don't mess with lowertext hex strings
+ looking_for_color = lowertext(looking_for_color)
+
+ if(!LAZYLEN(atom_colours))
+ // no atom colors list has been set up, just check the color var
+ return lowertext(color) == looking_for_color
+
+ for(var/i in min_priority_index to max_priority_index)
+ if(lowertext(atom_colours[i]) == looking_for_color)
+ return TRUE
+
+ return FALSE
+
+///Resets the atom's color to null, and then sets it to the highest priority colour available
+/atom/proc/update_atom_colour()
+ color = null
+ if(!atom_colours)
+ return
+ for(var/checked_color in atom_colours)
+ if(islist(checked_color))
+ var/list/color_list = checked_color
+ if(color_list.len)
+ color = color_list
+ return
+ else if(checked_color)
+ color = checked_color
+ return
diff --git a/code/game/atom_defense.dm b/code/game/atom/atom_defense.dm
similarity index 91%
rename from code/game/atom_defense.dm
rename to code/game/atom/atom_defense.dm
index d96ac9382d5..409b5a9e40b 100644
--- a/code/game/atom_defense.dm
+++ b/code/game/atom/atom_defense.dm
@@ -1,3 +1,17 @@
+/atom
+ ///any atom that uses integrity and can be damaged must set this to true, otherwise the integrity procs will throw an error
+ var/uses_integrity = FALSE
+
+ VAR_PROTECTED/datum/armor/armor_type = /datum/armor/none
+ VAR_PRIVATE/datum/armor/armor
+
+ VAR_PRIVATE/atom_integrity //defaults to max_integrity
+ var/max_integrity = 500
+ var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the atom breaks. 0.5 being 50%
+ ///Damage under this value will be completely ignored
+ var/damage_deflection = 0
+
+ var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
/// The essential proc to call when an atom must receive damage of any kind.
/atom/proc/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0)
diff --git a/code/game/atom/atom_examine.dm b/code/game/atom/atom_examine.dm
new file mode 100644
index 00000000000..a00f485391d
--- /dev/null
+++ b/code/game/atom/atom_examine.dm
@@ -0,0 +1,110 @@
+/atom
+ ///If non-null, overrides a/an/some in all cases
+ var/article
+
+/**
+ * Called when a mob examines (shift click or verb) this atom
+ *
+ * Default behaviour is to get the name and icon of the object and it's reagents where
+ * the [TRANSPARENT] flag is set on the reagents holder
+ *
+ * Produces a signal [COMSIG_ATOM_EXAMINE]
+ */
+/atom/proc/examine(mob/user)
+ var/examine_string = get_examine_string(user, thats = TRUE)
+ if(examine_string)
+ . = list("[examine_string].", EXAMINE_SECTION_BREAK) // SKYRAT EDIT CHANGE
+ else
+ . = list()
+
+ . += get_name_chaser(user)
+ if(desc)
+ . += desc
+
+ if(custom_materials)
+ // SKYRAT EDIT ADDITION BEGIN - HR sections
+ if(length(custom_materials) > 1)
+ . += EXAMINE_SECTION_BREAK //SKYRAT EDIT ADDITION
+ //SKYRAT EDIT ADDITION END
+ var/list/materials_list = list()
+ for(var/custom_material in custom_materials)
+ var/datum/material/current_material = GET_MATERIAL_REF(custom_material)
+ materials_list += "[current_material.name]"
+ . += "It is made out of [english_list(materials_list)]."
+ // SKYRAT EDIT ADDITION BEGIN - HR sections
+ if(length(custom_materials) > 1)
+ . += EXAMINE_SECTION_BREAK //SKYRAT EDIT ADDITION
+ //SKYRAT EDIT ADDITION END
+
+ if(reagents)
+ var/user_sees_reagents = user.can_see_reagents()
+ var/reagent_sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_REAGENT_EXAMINE, user, ., user_sees_reagents)
+ if(!(reagent_sigreturn & STOP_GENERIC_REAGENT_EXAMINE))
+ if(reagents.flags & TRANSPARENT)
+ if(reagents.total_volume)
+ . += "It contains [reagents.total_volume] units of various reagents[user_sees_reagents ? ":" : "."]"
+ if(user_sees_reagents) //Show each individual reagent for detailed examination
+ for(var/datum/reagent/current_reagent as anything in reagents.reagent_list)
+ . += "• [round(current_reagent.volume, CHEMICAL_VOLUME_ROUNDING)] units of [current_reagent.name]"
+ if(reagents.is_reacting)
+ . += span_warning("It is currently reacting!")
+ . += span_notice("The solution's pH is [round(reagents.ph, 0.01)] and has a temperature of [reagents.chem_temp]K.")
+
+ else
+ . += "It contains: Nothing."
+ else if(reagents.flags & AMOUNT_VISIBLE)
+ if(reagents.total_volume)
+ . += span_notice("It has [reagents.total_volume] unit\s left.")
+ else
+ . += span_danger("It's empty.")
+
+ SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .)
+
+/**
+ * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_WINDOW (default 1 second)
+ *
+ * This is where you can put extra information on something that may be superfluous or not important in critical gameplay
+ * moments, while allowing people to manually double-examine to take a closer look
+ *
+ * Produces a signal [COMSIG_ATOM_EXAMINE_MORE]
+ */
+/atom/proc/examine_more(mob/user)
+ SHOULD_CALL_PARENT(TRUE)
+ RETURN_TYPE(/list)
+
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE_MORE, user, .)
+ SEND_SIGNAL(user, COMSIG_MOB_EXAMINING_MORE, src, .)
+
+/**
+ * Get the name of this object for examine
+ *
+ * You can override what is returned from this proc by registering to listen for the
+ * [COMSIG_ATOM_GET_EXAMINE_NAME] signal
+ */
+/atom/proc/get_examine_name(mob/user)
+ . = "\a [src]"
+ var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]")
+ if(article)
+ . = "[article] [src]"
+ override[EXAMINE_POSITION_ARTICLE] = article
+ if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED)
+ . = override.Join("")
+
+///Generate the full examine string of this atom (including icon for goonchat)
+/atom/proc/get_examine_string(mob/user, thats = FALSE)
+ return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
+
+/**
+ * Returns an extended list of examine strings for any contained ID cards.
+ *
+ * Arguments:
+ * * user - The user who is doing the examining.
+ */
+/atom/proc/get_id_examine_strings(mob/user)
+ . = list()
+ return
+
+///Used to insert text after the name but before the description in examine()
+/atom/proc/get_name_chaser(mob/user, list/name_chaser = list())
+ return name_chaser
diff --git a/code/game/atom/atom_greyscale.dm b/code/game/atom/atom_greyscale.dm
new file mode 100644
index 00000000000..51d24b8528b
--- /dev/null
+++ b/code/game/atom/atom_greyscale.dm
@@ -0,0 +1,30 @@
+/atom
+ ///The config type to use for greyscaled sprites. Both this and greyscale_colors must be assigned to work.
+ var/greyscale_config
+ ///A string of hex format colors to be used by greyscale sprites, ex: "#0054aa#badcff"
+ var/greyscale_colors
+
+/// Handles updates to greyscale value updates.
+/// The colors argument can be either a list or the full color string.
+/// Child procs should call parent last so the update happens after all changes.
+/atom/proc/set_greyscale(list/colors, new_config)
+ SHOULD_CALL_PARENT(TRUE)
+ if(istype(colors))
+ colors = colors.Join("")
+ if(!isnull(colors) && greyscale_colors != colors) // If you want to disable greyscale stuff then give a blank string
+ greyscale_colors = colors
+
+ if(!isnull(new_config) && greyscale_config != new_config)
+ greyscale_config = new_config
+
+ update_greyscale()
+
+/// Checks if this atom uses the GAGS system and if so updates the icon
+/atom/proc/update_greyscale()
+ SHOULD_CALL_PARENT(TRUE)
+ if(greyscale_colors && greyscale_config)
+ icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)
+ if(!smoothing_flags) // This is a bitfield but we're just checking that some sort of smoothing is happening
+ return
+ update_atom_colour()
+ QUEUE_SMOOTH(src)
diff --git a/code/game/atom/atom_invisibility.dm b/code/game/atom/atom_invisibility.dm
new file mode 100644
index 00000000000..53fbe895817
--- /dev/null
+++ b/code/game/atom/atom_invisibility.dm
@@ -0,0 +1,72 @@
+// Index defines
+#define INVISIBILITY_VALUE 1
+#define INVISIBILITY_PRIORITY 2
+
+/atom
+ VAR_PRIVATE/list/invisibility_sources
+ VAR_PRIVATE/current_invisibility_priority = -INFINITY
+
+/atom/proc/RecalculateInvisibility()
+ PRIVATE_PROC(TRUE)
+
+ if(!invisibility_sources)
+ current_invisibility_priority = -INFINITY
+ invisibility = initial(invisibility)
+ return
+
+ var/highest_priority
+ var/list/highest_priority_invisibility_data
+ for(var/entry in invisibility_sources)
+ var/list/priority_data
+ if(islist(entry))
+ priority_data = entry
+ else
+ priority_data = invisibility_sources[entry]
+
+ var/priority = priority_data[INVISIBILITY_PRIORITY]
+ if(highest_priority > priority) // In the case of equal priorities, we use the last thing in the list so that more recent changes apply first
+ continue
+
+ highest_priority = priority
+ highest_priority_invisibility_data = priority_data
+
+ current_invisibility_priority = highest_priority
+ invisibility = highest_priority_invisibility_data[INVISIBILITY_VALUE]
+
+/**
+ * Sets invisibility according to priority.
+ * If you want to be able to undo the value you set back to what it would be otherwise,
+ * you should provide an id here and remove it using RemoveInvisibility(id)
+ */
+/atom/proc/SetInvisibility(desired_value, id, priority=0)
+ if(!invisibility_sources)
+ invisibility_sources = list()
+
+ if(id)
+ invisibility_sources[id] = list(desired_value, priority)
+ else
+ invisibility_sources += list(list(desired_value, priority))
+
+ if(current_invisibility_priority > priority)
+ return
+
+ RecalculateInvisibility()
+
+/// Removes the specified invisibility source from the tracker
+/atom/proc/RemoveInvisibility(id)
+ if(!invisibility_sources)
+ return
+
+ var/list/priority_data = invisibility_sources[id]
+ invisibility_sources -= id
+
+ if(length(invisibility_sources) == 0)
+ invisibility_sources = null
+
+ if(current_invisibility_priority > priority_data[INVISIBILITY_PRIORITY])
+ return
+
+ RecalculateInvisibility()
+
+#undef INVISIBILITY_VALUE
+#undef INVISIBILITY_PRIORITY
diff --git a/code/game/atom/atom_materials.dm b/code/game/atom/atom_materials.dm
new file mode 100644
index 00000000000..ae9a5b3a18e
--- /dev/null
+++ b/code/game/atom/atom_materials.dm
@@ -0,0 +1,128 @@
+/atom
+ ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
+ ///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials].
+ var/list/datum/material/custom_materials
+ ///Bitfield for how the atom handles materials.
+ var/material_flags = NONE
+ ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines.
+ var/material_modifier = 1
+
+/// Sets the custom materials for an item.
+/atom/proc/set_custom_materials(list/materials, multiplier = 1)
+ if(custom_materials && material_flags & MATERIAL_EFFECTS) //Only runs if custom materials existed at first and affected src.
+ for(var/current_material in custom_materials)
+ var/datum/material/custom_material = GET_MATERIAL_REF(current_material)
+ custom_material.on_removed(src, custom_materials[current_material] * material_modifier, material_flags) //Remove the current materials
+
+ if(!length(materials))
+ custom_materials = null
+ return
+
+ if(material_flags & MATERIAL_EFFECTS)
+ for(var/current_material in materials)
+ var/datum/material/custom_material = GET_MATERIAL_REF(current_material)
+ custom_material.on_applied(src, materials[current_material] * multiplier * material_modifier, material_flags)
+
+ custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier)
+
+/**
+ * Returns the material composition of the atom.
+ *
+ * Used when recycling items, specifically to turn alloys back into their component mats.
+ *
+ * Exists because I'd need to add a way to un-alloy alloys or otherwise deal
+ * with people converting the entire stations material supply into alloys.
+ *
+ * Arguments:
+ * - flags: A set of flags determining how exactly the materials are broken down.
+ */
+/atom/proc/get_material_composition(breakdown_flags=NONE)
+ . = list()
+ if(!(breakdown_flags & BREAKDOWN_INCLUDE_ALCHEMY) && HAS_TRAIT(src, TRAIT_MAT_TRANSMUTED))
+ return
+
+ var/list/cached_materials = custom_materials
+ for(var/mat in cached_materials)
+ var/datum/material/material = GET_MATERIAL_REF(mat)
+ var/list/material_comp = material.return_composition(cached_materials[mat], breakdown_flags)
+ for(var/comp_mat in material_comp)
+ .[comp_mat] += material_comp[comp_mat]
+
+/**
+ * Fetches a list of all of the materials this object has of the desired type. Returns null if there is no valid materials of the type
+ *
+ * Arguments:
+ * - [required_material][/datum/material]: The type of material we are checking for
+ * - mat_amount: The minimum required amount of material
+ */
+/atom/proc/has_material_type(datum/material/required_material, mat_amount = 0)
+ var/list/cached_materials = custom_materials
+ if(!length(cached_materials))
+ return null
+
+ var/materials_of_type
+ for(var/current_material in cached_materials)
+ if(cached_materials[current_material] < mat_amount)
+ continue
+ var/datum/material/material = GET_MATERIAL_REF(current_material)
+ if(!istype(material, required_material))
+ continue
+ LAZYSET(materials_of_type, material, cached_materials[current_material])
+
+ return materials_of_type
+
+/**
+ * Fetches a list of all of the materials this object has with the desired material category.
+ *
+ * Arguments:
+ * - category: The category to check for
+ * - any_flags: Any bitflags that must be present for the category
+ * - all_flags: All bitflags that must be present for the category
+ * - no_flags: Any bitflags that must not be present for the category
+ * - mat_amount: The minimum amount of materials that must be present
+ */
+/atom/proc/has_material_category(category, any_flags=0, all_flags=0, no_flags=0, mat_amount=0)
+ var/list/cached_materials = custom_materials
+ if(!length(cached_materials))
+ return null
+
+ var/materials_of_category
+ for(var/current_material in cached_materials)
+ if(cached_materials[current_material] < mat_amount)
+ continue
+ var/datum/material/material = GET_MATERIAL_REF(current_material)
+ var/category_flags = material?.categories[category]
+ if(isnull(category_flags))
+ continue
+ if(any_flags && !(category_flags & any_flags))
+ continue
+ if(all_flags && (all_flags != (category_flags & all_flags)))
+ continue
+ if(no_flags && (category_flags & no_flags))
+ continue
+ LAZYSET(materials_of_category, material, cached_materials[current_material])
+ return materials_of_category
+
+/**
+ * Gets the most common material in the object.
+ */
+/atom/proc/get_master_material()
+ var/list/cached_materials = custom_materials
+ if(!length(cached_materials))
+ return null
+
+ var/most_common_material = null
+ var/max_amount = 0
+ for(var/material in cached_materials)
+ if(cached_materials[material] > max_amount)
+ most_common_material = material
+ max_amount = cached_materials[material]
+
+ if(most_common_material)
+ return GET_MATERIAL_REF(most_common_material)
+
+/**
+ * Gets the total amount of materials in this atom.
+ */
+/atom/proc/get_custom_material_amount()
+ return isnull(custom_materials) ? 0 : counterlist_sum(custom_materials)
diff --git a/code/game/atom/atom_merger.dm b/code/game/atom/atom_merger.dm
new file mode 100644
index 00000000000..1787d51675b
--- /dev/null
+++ b/code/game/atom/atom_merger.dm
@@ -0,0 +1,14 @@
+/atom
+ ///Holds merger groups currently active on the atom. Do not access directly, use GetMergeGroup() instead.
+ var/list/datum/merger/mergers
+
+/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
+/atom/proc/GetMergeGroup(id, list/allowed_types)
+ RETURN_TYPE(/datum/merger)
+ var/datum/merger/candidate
+ if(mergers)
+ candidate = mergers[id]
+ if(!candidate)
+ new /datum/merger(id, allowed_types, src)
+ candidate = mergers[id]
+ return candidate
diff --git a/code/game/atom/atom_orbit.dm b/code/game/atom/atom_orbit.dm
new file mode 100644
index 00000000000..8eecddd952d
--- /dev/null
+++ b/code/game/atom/atom_orbit.dm
@@ -0,0 +1,33 @@
+/atom
+ ///Reference to atom being orbited
+ var/atom/orbit_target
+ ///The orbiter component, if there's anything orbiting this atom
+ var/datum/component/orbiter/orbiters
+
+/**
+ * Recursive getter method to return a list of all ghosts orbitting this atom
+ *
+ * This will work fine without manually passing arguments.
+ * * processed - The list of atoms we've already convered
+ * * source - Is this the atom for who we're counting up all the orbiters?
+ * * ignored_stealthed_admins - If TRUE, don't count admins who are stealthmoded and orbiting this
+ */
+/atom/proc/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
+ var/list/output = list()
+ if(!processed)
+ processed = list()
+ else if(src in processed)
+ return output
+
+ if(!source)
+ output += src
+
+ processed += src
+ for(var/atom/atom_orbiter as anything in orbiters?.orbiter_list)
+ output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
+ return output
+
+/mob/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
+ if(!source && ignore_stealthed_admins && client?.holder?.fakekey)
+ return list()
+ return ..()
diff --git a/code/game/atom/atom_storage.dm b/code/game/atom/atom_storage.dm
new file mode 100644
index 00000000000..6273c28fc2e
--- /dev/null
+++ b/code/game/atom/atom_storage.dm
@@ -0,0 +1,41 @@
+/atom
+ /// the datum handler for our contents - see create_storage() for creation method
+ var/datum/storage/atom_storage
+
+/// A quick and easy way to create a storage datum for an atom
+/atom/proc/create_storage(
+ max_slots,
+ max_specific_storage,
+ max_total_storage,
+ numerical_stacking = FALSE,
+ allow_quick_gather = FALSE,
+ allow_quick_empty = FALSE,
+ collection_mode = COLLECT_ONE,
+ attack_hand_interact = TRUE,
+ list/canhold,
+ list/canthold,
+ storage_type = /datum/storage,
+)
+
+ if(atom_storage)
+ QDEL_NULL(atom_storage)
+
+ atom_storage = new storage_type(src, max_slots, max_specific_storage, max_total_storage, numerical_stacking, allow_quick_gather, collection_mode, attack_hand_interact)
+
+ if(canhold || canthold)
+ atom_storage.set_holdable(canhold, canthold)
+
+ return atom_storage
+
+/// A quick and easy way to /clone/ a storage datum for an atom (does not copy over contents, only the datum details)
+/atom/proc/clone_storage(datum/storage/cloning)
+ if(atom_storage)
+ QDEL_NULL(atom_storage)
+
+ atom_storage = new cloning.type(src, cloning.max_slots, cloning.max_specific_storage, cloning.max_total_storage, cloning.numerical_stacking, cloning.allow_quick_gather, cloning.collection_mode, cloning.attack_hand_interact)
+
+ if(cloning.can_hold || cloning.cant_hold)
+ if(!atom_storage.can_hold && !atom_storage.cant_hold) //In the event that the can/can't hold lists are already in place (such as from storage objects added on initialize).
+ atom_storage.set_holdable(cloning.can_hold, cloning.cant_hold)
+
+ return atom_storage
diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm
new file mode 100644
index 00000000000..f6ce7eb96c7
--- /dev/null
+++ b/code/game/atom/atom_tool_acts.dm
@@ -0,0 +1,120 @@
+/**
+ *Tool behavior procedure. Redirects to tool-specific procs by default.
+ *
+ * You can override it to catch all tool interactions, for use in complex deconstruction procs.
+ *
+ * Must return parent proc ..() in the end if overridden
+ */
+/atom/proc/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
+ var/act_result
+ var/signal_result
+
+ var/is_left_clicking = !is_right_clicking
+
+ if(is_left_clicking) // Left click first for sensibility
+ var/list/processing_recipes = list() //List of recipes that can be mutated by sending the signal
+ signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, tool, processing_recipes)
+ if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
+ return TOOL_ACT_SIGNAL_BLOCKING
+ if(processing_recipes.len)
+ process_recipes(user, tool, processing_recipes)
+ if(QDELETED(tool))
+ return TRUE
+ else
+ signal_result = SEND_SIGNAL(src, COMSIG_ATOM_SECONDARY_TOOL_ACT(tool_type), user, tool)
+ if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
+ return TOOL_ACT_SIGNAL_BLOCKING
+
+ switch(tool_type)
+ if(TOOL_CROWBAR)
+ act_result = is_left_clicking ? crowbar_act(user, tool) : crowbar_act_secondary(user, tool)
+ if(TOOL_MULTITOOL)
+ act_result = is_left_clicking ? multitool_act(user, tool) : multitool_act_secondary(user, tool)
+ if(TOOL_SCREWDRIVER)
+ act_result = is_left_clicking ? screwdriver_act(user, tool) : screwdriver_act_secondary(user, tool)
+ if(TOOL_WRENCH)
+ act_result = is_left_clicking ? wrench_act(user, tool) : wrench_act_secondary(user, tool)
+ if(TOOL_WIRECUTTER)
+ act_result = is_left_clicking ? wirecutter_act(user, tool) : wirecutter_act_secondary(user, tool)
+ if(TOOL_WELDER)
+ act_result = is_left_clicking ? welder_act(user, tool) : welder_act_secondary(user, tool)
+ if(TOOL_ANALYZER)
+ act_result = is_left_clicking ? analyzer_act(user, tool) : analyzer_act_secondary(user, tool)
+ if(!act_result)
+ return
+
+ // A tooltype_act has completed successfully
+ if(is_left_clicking)
+ log_tool("[key_name(user)] used [tool] on [src] at [AREACOORD(src)]")
+ SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_PRIMARY(tool_type), src)
+ else
+ log_tool("[key_name(user)] used [tool] on [src] (right click) at [AREACOORD(src)]")
+ SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_SECONDARY(tool_type), src)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+//! Tool-specific behavior procs.
+///
+
+/// Called on an object when a tool with crowbar capabilities is used to left click an object
+/atom/proc/crowbar_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with crowbar capabilities is used to right click an object
+/atom/proc/crowbar_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with multitool capabilities is used to left click an object
+/atom/proc/multitool_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with multitool capabilities is used to right click an object
+/atom/proc/multitool_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+///Check if an item supports a data buffer (is a multitool)
+/atom/proc/multitool_check_buffer(user, obj/item/multitool, silent = FALSE)
+ if(!istype(multitool, /obj/item/multitool))
+ if(user && !silent)
+ to_chat(user, span_warning("[multitool] has no data buffer!"))
+ return FALSE
+ return TRUE
+
+/// Called on an object when a tool with screwdriver capabilities is used to left click an object
+/atom/proc/screwdriver_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with screwdriver capabilities is used to right click an object
+/atom/proc/screwdriver_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with wrench capabilities is used to left click an object
+/atom/proc/wrench_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with wrench capabilities is used to right click an object
+/atom/proc/wrench_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with wirecutter capabilities is used to left click an object
+/atom/proc/wirecutter_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with wirecutter capabilities is used to right click an object
+/atom/proc/wirecutter_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with welder capabilities is used to left click an object
+/atom/proc/welder_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with welder capabilities is used to right click an object
+/atom/proc/welder_act_secondary(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with analyzer capabilities is used to left click an object
+/atom/proc/analyzer_act(mob/living/user, obj/item/tool)
+ return
+
+/// Called on an object when a tool with analyzer capabilities is used to right click an object
+/atom/proc/analyzer_act_secondary(mob/living/user, obj/item/tool)
+ return
diff --git a/code/game/atom/atom_vv.dm b/code/game/atom/atom_vv.dm
new file mode 100644
index 00000000000..8393ecabcfd
--- /dev/null
+++ b/code/game/atom/atom_vv.dm
@@ -0,0 +1,271 @@
+/**
+ * Return the markup to for the dropdown list for the VV panel for this atom
+ *
+ * Override in subtypes to add custom VV handling in the VV panel
+ */
+/atom/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION("", "---------")
+ if(!ismovable(src))
+ var/turf/curturf = get_turf(src)
+ if(curturf)
+ . += ""
+ VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform")
+ VV_DROPDOWN_OPTION(VV_HK_SPIN_ANIMATION, "SpinAnimation")
+ VV_DROPDOWN_OPTION(VV_HK_STOP_ALL_ANIMATIONS, "Stop All Animations")
+ VV_DROPDOWN_OPTION(VV_HK_SHOW_HIDDENPRINTS, "Show Hiddenprint log")
+ VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent")
+ VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
+ VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
+ VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
+ VV_DROPDOWN_OPTION(VV_HK_EDIT_COLOR_MATRIX, "Edit Color as Matrix")
+ VV_DROPDOWN_OPTION(VV_HK_TEST_MATRIXES, "Test Matrices")
+ VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
+ VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify Armor")
+ if(greyscale_colors)
+ VV_DROPDOWN_OPTION(VV_HK_MODIFY_GREYSCALE, "Modify greyscale colors")
+
+/atom/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT))
+ if(!reagents)
+ var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null
+ if(amount)
+ create_reagents(amount)
+
+ if(reagents)
+ var/chosen_id
+ switch(tgui_alert(usr, "Choose a method.", "Add Reagents", list("Search", "Choose from a list", "I'm feeling lucky")))
+ if("Search")
+ var/valid_id
+ while(!valid_id)
+ chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text
+ if(isnull(chosen_id)) //Get me out of here!
+ break
+ if (!ispath(text2path(chosen_id)))
+ chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent)))
+ if (ispath(chosen_id))
+ valid_id = TRUE
+ else
+ valid_id = TRUE
+ if(!valid_id)
+ to_chat(usr, span_warning("A reagent with that ID doesn't exist!"))
+ if("Choose from a list")
+ chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sort_list(subtypesof(/datum/reagent), GLOBAL_PROC_REF(cmp_typepaths_asc))
+ if("I'm feeling lucky")
+ chosen_id = pick(subtypesof(/datum/reagent))
+ if(chosen_id)
+ var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null
+ if(amount)
+ reagents.add_reagent(chosen_id, amount)
+ log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]")
+ message_admins(span_notice("[key_name(usr)] has added [amount] units of [chosen_id] to [src]"))
+
+ if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN))
+ usr.client.cmd_admin_explosion(src)
+
+ if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN))
+ usr.client.cmd_admin_emp(src)
+
+ if(href_list[VV_HK_SHOW_HIDDENPRINTS] && check_rights(R_ADMIN))
+ usr.client.cmd_show_hiddenprints(src)
+
+ if(href_list[VV_HK_ARMOR_MOD])
+ var/list/pickerlist = list()
+ var/list/armorlist = get_armor().get_rating_list()
+
+ for (var/i in armorlist)
+ pickerlist += list(list("value" = armorlist[i], "name" = i))
+
+ var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist)
+ var/list/armor_all = ARMOR_LIST_ALL()
+
+ if (islist(result))
+ if (result["button"] != 2) // If the user pressed the cancel button
+ // text2num conveniently returns a null on invalid values
+ var/list/converted = list()
+ for(var/armor_key in armor_all)
+ converted[armor_key] = text2num(result["values"][armor_key])
+ set_armor(get_armor().generate_new_with_specific(converted))
+ var/message = "[key_name(usr)] modified the armor on [src] ([type]) to: "
+ for(var/armor_key in armor_all)
+ message += "[armor_key]=[get_armor_rating(armor_key)],"
+ message = copytext(message, 1, -1)
+ log_admin(span_notice(message))
+ message_admins(span_notice(message))
+
+ if(href_list[VV_HK_ADD_AI])
+ if(!check_rights(R_VAREDIT))
+ return
+ var/result = input(usr, "Choose the AI controller to apply to this atom WARNING: Not all AI works on all atoms.", "AI controller") as null|anything in subtypesof(/datum/ai_controller)
+ if(!result)
+ return
+ ai_controller = new result(src)
+
+ if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT))
+ var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate","Shear")
+ var/matrix/M = transform
+ if(!result)
+ return
+ switch(result)
+ if("Scale")
+ var/x = input(usr, "Choose x mod","Transform Mod") as null|num
+ var/y = input(usr, "Choose y mod","Transform Mod") as null|num
+ if(isnull(x) || isnull(y))
+ return
+ transform = M.Scale(x,y)
+ if("Translate")
+ var/x = input(usr, "Choose x mod (negative = left, positive = right)","Transform Mod") as null|num
+ var/y = input(usr, "Choose y mod (negative = down, positive = up)","Transform Mod") as null|num
+ if(isnull(x) || isnull(y))
+ return
+ transform = M.Translate(x,y)
+ if("Shear")
+ var/x = input(usr, "Choose x mod","Transform Mod") as null|num
+ var/y = input(usr, "Choose y mod","Transform Mod") as null|num
+ if(isnull(x) || isnull(y))
+ return
+ transform = M.Shear(x,y)
+ if("Rotate")
+ var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num
+ if(isnull(angle))
+ return
+ transform = M.Turn(angle)
+
+ SEND_SIGNAL(src, COMSIG_ATOM_VV_MODIFY_TRANSFORM)
+
+ if(href_list[VV_HK_SPIN_ANIMATION] && check_rights(R_VAREDIT))
+ var/num_spins = input(usr, "Do you want infinite spins?", "Spin Animation") in list("Yes", "No")
+ if(num_spins == "No")
+ num_spins = input(usr, "How many spins?", "Spin Animation") as null|num
+ else
+ num_spins = -1
+ if(!num_spins)
+ return
+ var/spin_speed = input(usr, "How fast?", "Spin Animation") as null|num
+ if(!spin_speed)
+ return
+ var/direction = input(usr, "Which direction?", "Spin Animation") in list("Clockwise", "Counter-clockwise")
+ switch(direction)
+ if("Clockwise")
+ direction = 1
+ if("Counter-clockwise")
+ direction = 0
+ else
+ return
+ SpinAnimation(spin_speed, num_spins, direction)
+
+ if(href_list[VV_HK_STOP_ALL_ANIMATIONS] && check_rights(R_VAREDIT))
+ var/result = input(usr, "Are you sure?", "Stop Animating") in list("Yes", "No")
+ if(result == "Yes")
+ animate(src, transform = null, flags = ANIMATION_END_NOW) // Literally just fucking stop animating entirely because admin said so
+ return
+
+ if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT))
+ var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
+ // Check the new name against the chat filter. If it triggers the IC chat filter, give an option to confirm.
+ if(newname && !(is_ic_filtered(newname) || is_soft_ic_filtered(newname) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) != "Confirm"))
+ vv_auto_rename(newname)
+
+ if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
+ usr.client?.open_filter_editor(src)
+
+ if(href_list[VV_HK_EDIT_COLOR_MATRIX] && check_rights(R_VAREDIT))
+ usr.client?.open_color_matrix_editor(src)
+
+ if(href_list[VV_HK_TEST_MATRIXES] && check_rights(R_VAREDIT))
+ usr.client?.open_matrix_tester(src)
+
+/atom/vv_get_header()
+ . = ..()
+ var/refid = REF(src)
+ . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]"
+ . += " <<[dir2text(dir) || dir]>>"
+
+/**
+ * call back when a var is edited on this atom
+ *
+ * Can be used to implement special handling of vars
+ *
+ * At the atom level, if you edit a var named "color" it will add the atom colour with
+ * admin level priority to the atom colours list
+ *
+ * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies
+ * the object has been admin edited
+ */
+/atom/vv_edit_var(var_name, var_value)
+ var/old_light_flags = light_flags
+ // Disable frozen lights for now, so we can actually modify it
+ light_flags &= ~LIGHT_FROZEN
+ switch(var_name)
+ if(NAMEOF(src, light_range))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_range = var_value)
+ else
+ set_light_range(var_value)
+ . = TRUE
+ if(NAMEOF(src, light_power))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_power = var_value)
+ else
+ set_light_power(var_value)
+ . = TRUE
+ if(NAMEOF(src, light_color))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_color = var_value)
+ else
+ set_light_color(var_value)
+ . = TRUE
+ if(NAMEOF(src, light_angle))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_angle = var_value)
+ . = TRUE
+ if(NAMEOF(src, light_dir))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_dir = var_value)
+ . = TRUE
+ if(NAMEOF(src, light_height))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_height = var_value)
+ . = TRUE
+ if(NAMEOF(src, light_on))
+ if(light_system == STATIC_LIGHT)
+ set_light(l_on = var_value)
+ else
+ set_light_on(var_value)
+ . = TRUE
+ if(NAMEOF(src, light_flags))
+ set_light_flags(var_value)
+ // I'm sorry
+ old_light_flags = var_value
+ . = TRUE
+ if(NAMEOF(src, smoothing_junction))
+ set_smoothed_icon_state(var_value)
+ . = TRUE
+ if(NAMEOF(src, opacity))
+ set_opacity(var_value)
+ . = TRUE
+ if(NAMEOF(src, base_pixel_x))
+ set_base_pixel_x(var_value)
+ . = TRUE
+ if(NAMEOF(src, base_pixel_y))
+ set_base_pixel_y(var_value)
+ . = TRUE
+
+ light_flags = old_light_flags
+ if(!isnull(.))
+ datum_flags |= DF_VAR_EDITED
+ return
+
+ if(!GLOB.Debug2)
+ flags_1 |= ADMIN_SPAWNED_1
+
+ . = ..()
+
+ switch(var_name)
+ if(NAMEOF(src, color))
+ add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
+ update_appearance()
+
+/atom/proc/vv_auto_rename(newname)
+ name = newname
diff --git a/code/game/atoms_initializing_EXPENSIVE.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm
similarity index 76%
rename from code/game/atoms_initializing_EXPENSIVE.dm
rename to code/game/atom/atoms_initializing_EXPENSIVE.dm
index 1ecc6390edc..2a3a442e04d 100644
--- a/code/game/atoms_initializing_EXPENSIVE.dm
+++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm
@@ -52,6 +52,27 @@
return qdeleted || QDELING(A)
+/**
+ * Called when an atom is created in byond (built in engine proc)
+ *
+ * Not a lot happens here in SS13 code, as we offload most of the work to the
+ * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader
+ * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate
+ * result is that the Intialize proc is called.
+ *
+ */
+/atom/New(loc, ...)
+ //atom creation method that preloads variables at creation
+ if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instanciated atom is creating other atoms in New()
+ world.preloader_load(src)
+
+ var/do_initialize = SSatoms.initialized
+ if(do_initialize != INITIALIZATION_INSSATOMS)
+ args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
+ if(SSatoms.InitAtom(src, FALSE, args))
+ //we were deleted
+ return
+
/**
* The primary method that objects are setup in SS13 with
*
@@ -126,3 +147,17 @@
ai_controller = new ai_controller(src)
return INITIALIZE_HINT_NORMAL
+
+/**
+ * Late Intialization, for code that should run after all atoms have run Intialization
+ *
+ * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize]
+ * proc must return the hint
+ * [INITIALIZE_HINT_LATELOAD] otherwise it will never be called.
+ *
+ * useful for doing things like finding other machines on GLOB.machines because you can guarantee
+ * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
+ * code has been run
+ */
+/atom/proc/LateInitialize()
+ set waitfor = FALSE
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
deleted file mode 100644
index b6e3dbb760c..00000000000
--- a/code/game/atoms.dm
+++ /dev/null
@@ -1,2323 +0,0 @@
-/**
- * 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
- layer = 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
-
- ///If non-null, overrides a/an/some in all cases
- var/article
-
- ///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
-
- /**
- * used to store the different colors on an atom
- *
- * its inherent color, the colored paint applied on it, special color effect etc...
- */
- var/list/atom_colours
-
-
- /// a very temporary list of overlays to remove
- var/list/remove_overlays
- /// a very temporary list of overlays to add
- var/list/add_overlays
-
- ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays.
- var/list/managed_vis_overlays
- ///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
-
- /// 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
-
- //List of datums orbiting this atom
- var/datum/component/orbiter/orbiters
-
- /// 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
-
- ///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
- ///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials].
- var/list/datum/material/custom_materials
- ///Bitfield for how the atom handles materials.
- var/material_flags = NONE
- ///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines.
- var/material_modifier = 1
-
- var/datum/wires/wires = null
-
- var/list/alternate_appearances
-
- ///Light systems, both shouldn't be active at the same time.
- var/light_system = STATIC_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
-
- ///The config type to use for greyscaled sprites. Both this and greyscale_colors must be assigned to work.
- var/greyscale_config
- ///A string of hex format colors to be used by greyscale sprites, ex: "#0054aa#badcff"
- var/greyscale_colors
-
- ///Holds merger groups currently active on the atom. Do not access directly, use GetMergeGroup() instead.
- var/list/datum/merger/mergers
-
- ///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 //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.
- ///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. 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
- ///Reference to atom being orbited
- var/atom/orbit_target
- ///AI controller that controls this atom. type on init, then turned into an instance during runtime
- var/datum/ai_controller/ai_controller
-
- ///any atom that uses integrity and can be damaged must set this to true, otherwise the integrity procs will throw an error
- var/uses_integrity = FALSE
-
- VAR_PROTECTED/datum/armor/armor_type = /datum/armor/none
- VAR_PRIVATE/datum/armor/armor
-
- VAR_PRIVATE/atom_integrity //defaults to max_integrity
- var/max_integrity = 500
- var/integrity_failure = 0 //0 if we have no special broken behavior, otherwise is a percentage of at what point the atom breaks. 0.5 being 50%
- ///Damage under this value will be completely ignored
- var/damage_deflection = 0
-
- var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
- /// forensics datum, contains fingerprints, fibres, blood_dna and hiddenprints on this atom
- var/datum/forensics/forensics
-
- /// the datum handler for our contents - see create_storage() for creation method
- var/datum/storage/atom_storage
- /// How this atom should react to having its astar blocking checked
- var/can_astar_pass = CANASTARPASS_DENSITY
-
- VAR_PRIVATE/list/invisibility_sources
- VAR_PRIVATE/current_invisibility_priority = -INFINITY
-
-/**
- * Called when an atom is created in byond (built in engine proc)
- *
- * Not a lot happens here in SS13 code, as we offload most of the work to the
- * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader
- * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate
- * result is that the Intialize proc is called.
- *
- */
-/atom/New(loc, ...)
- //atom creation method that preloads variables at creation
- if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instanciated atom is creating other atoms in New()
- world.preloader_load(src)
-
- var/do_initialize = SSatoms.initialized
- if(do_initialize != INITIALIZATION_INSSATOMS)
- args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
- if(SSatoms.InitAtom(src, FALSE, args))
- //we were deleted
- return
-
-/**
- * Late Intialization, for code that should run after all atoms have run Intialization
- *
- * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize]
- * proc must return the hint
- * [INITIALIZE_HINT_LATELOAD] otherwise it will never be called.
- *
- * useful for doing things like finding other machines on GLOB.machines because you can guarantee
- * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
- * code has been run
- */
-/atom/proc/LateInitialize()
- set waitfor = 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)
-
- 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)
-
- return ..()
-
-/// A quick and easy way to create a storage datum for an atom
-/atom/proc/create_storage(
- max_slots,
- max_specific_storage,
- max_total_storage,
- numerical_stacking = FALSE,
- allow_quick_gather = FALSE,
- allow_quick_empty = FALSE,
- collection_mode = COLLECT_ONE,
- attack_hand_interact = TRUE,
- list/canhold,
- list/canthold,
- storage_type = /datum/storage,
-)
-
- if(atom_storage)
- QDEL_NULL(atom_storage)
-
- atom_storage = new storage_type(src, max_slots, max_specific_storage, max_total_storage, numerical_stacking, allow_quick_gather, collection_mode, attack_hand_interact)
-
- if(canhold || canthold)
- atom_storage.set_holdable(canhold, canthold)
-
- return atom_storage
-
-/// A quick and easy way to /clone/ a storage datum for an atom (does not copy over contents, only the datum details)
-/atom/proc/clone_storage(datum/storage/cloning)
- if(atom_storage)
- QDEL_NULL(atom_storage)
-
- atom_storage = new cloning.type(src, cloning.max_slots, cloning.max_specific_storage, cloning.max_total_storage, cloning.numerical_stacking, cloning.allow_quick_gather, cloning.collection_mode, cloning.attack_hand_interact)
-
- if(cloning.can_hold || cloning.cant_hold)
- if(!atom_storage.can_hold && !atom_storage.cant_hold) //In the event that the can/can't hold lists are already in place (such as from storage objects added on initialize).
- atom_storage.set_holdable(cloning.can_hold, cloning.cant_hold)
-
- return atom_storage
-
-/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(current_turf in shuttle_area.get_contained_turfs())
- 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
-
-
-
-///This atom has been hit by a hulkified mob in hulk mode (user)
-/atom/proc/attack_hulk(mob/living/carbon/human/user)
- SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
-
-/**
- * Ensure a list of atoms/reagents exists inside this atom
- *
- * Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder
- * creating the reagent holder if it exists.
- *
- * If the part is a moveable atom and the previous location of the item was a mob/living,
- * it calls the inventory handler transferItemToLoc for that mob/living and transfers the part
- * to this atom
- *
- * Otherwise it simply forceMoves the atom into this atom
- */
-/atom/proc/CheckParts(list/parts_list, datum/crafting_recipe/current_recipe)
- SEND_SIGNAL(src, COMSIG_ATOM_CHECKPARTS, parts_list, current_recipe)
- if(!parts_list)
- return
- for(var/part in parts_list)
- if(istype(part, /datum/reagent))
- if(!reagents)
- reagents = new()
- reagents.reagent_list.Add(part)
- reagents.conditional_update()
- else if(ismovable(part))
- var/atom/movable/object = part
- if(isliving(object.loc))
- var/mob/living/living = object.loc
- living.transferItemToLoc(object, src)
- else
- object.forceMove(src)
- SEND_SIGNAL(object, COMSIG_ATOM_USED_IN_CRAFT, src)
- parts_list.Cut()
-
-///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
-
-///Check if this atoms eye is still alive (probably)
-/atom/proc/check_eye(mob/user)
- SIGNAL_HANDLER
- return
-
-/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(mob/user, allowmobs = TRUE)
- return reagents && (reagents.flags & (INJECTABLE | REFILLABLE))
-
-/// Can we draw from this atom with an injectable atom
-/atom/proc/is_drawable(mob/user, allowmobs = TRUE)
- 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.
- * - volume_modifier: Volume multiplier.
- * - show_message: Whether to display anything to mobs when they are exposed.
- */
-/atom/proc/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
- . = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
- if(. & COMPONENT_NO_EXPOSE_REAGENTS)
- return
-
- SEND_SIGNAL(source, COMSIG_REAGENTS_EXPOSE_ATOM, src, reagents, methods, volume_modifier, show_message)
- for(var/datum/reagent/current_reagent as anything in reagents)
- . |= current_reagent.expose_atom(src, reagents[current_reagent])
- SEND_SIGNAL(src, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
-
-/// 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
-
-/**
- * React to an EMP of the given severity
- *
- * Default behaviour is to send the [COMSIG_ATOM_PRE_EMP_ACT] and [COMSIG_ATOM_EMP_ACT] signal
- *
- * If the pre-signal does not return protection, and there are attached wires then we call
- * [emp_pulse][/datum/wires/proc/emp_pulse] on the wires
- *
- * We then return the protection value
- */
-/atom/proc/emp_act(severity)
- var/protection = SEND_SIGNAL(src, COMSIG_ATOM_PRE_EMP_ACT, severity)
- if(!(protection & EMP_PROTECT_WIRES) && istype(wires))
- wires.emp_pulse()
-
- SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity, protection)
- return protection // Pass the protection value collected here upwards
-
-/**
- * React to a hit by a projectile object
- *
- * @params
- * * hitting_projectile - projectile
- * * def_zone - zone hit
- * * piercing_hit - is this hit piercing or normal?
- */
-/atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
- SHOULD_CALL_PARENT(TRUE)
-
- var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone)
- if(sigreturn & COMPONENT_BULLET_PIERCED)
- return BULLET_ACT_FORCE_PIERCE
- if(sigreturn & COMPONENT_BULLET_BLOCKED)
- return BULLET_ACT_BLOCK
- if(sigreturn & COMPONENT_BULLET_ACTED)
- return BULLET_ACT_HIT
-
- SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone)
- if(QDELETED(hitting_projectile)) // Signal deleted it?
- return BULLET_ACT_BLOCK
-
- return hitting_projectile.on_hit(
- target = src,
- // This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since
- // only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot.
- blocked = check_projectile_armor(def_zone, hitting_projectile),
- pierce_hit = piercing_hit,
- )
-
-///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
-
-/**
- * Get the name of this object for examine
- *
- * You can override what is returned from this proc by registering to listen for the
- * [COMSIG_ATOM_GET_EXAMINE_NAME] signal
- */
-/atom/proc/get_examine_name(mob/user)
- . = "\a [src]"
- var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]")
- if(article)
- . = "[article] [src]"
- override[EXAMINE_POSITION_ARTICLE] = article
- if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED)
- . = override.Join("")
-
-///Generate the full examine string of this atom (including icon for goonchat)
-/atom/proc/get_examine_string(mob/user, thats = FALSE)
- return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
-
-/**
- * Returns an extended list of examine strings for any contained ID cards.
- *
- * Arguments:
- * * user - The user who is doing the examining.
- */
-/atom/proc/get_id_examine_strings(mob/user)
- . = list()
- return
-
-///Used to insert text after the name but before the description in examine()
-/atom/proc/get_name_chaser(mob/user, list/name_chaser = list())
- return name_chaser
-
-/**
- * Called when a mob examines (shift click or verb) this atom
- *
- * Default behaviour is to get the name and icon of the object and it's reagents where
- * the [TRANSPARENT] flag is set on the reagents holder
- *
- * Produces a signal [COMSIG_ATOM_EXAMINE]
- */
-/atom/proc/examine(mob/user)
- var/examine_string = get_examine_string(user, thats = TRUE)
- if(examine_string)
- . = list("[examine_string].", EXAMINE_SECTION_BREAK) // SKYRAT EDIT CHANGE
- else
- . = list()
-
- . += get_name_chaser(user)
- if(desc)
- . += desc
-
- if(custom_materials)
- // SKYRAT EDIT ADDITION BEGIN - HR sections
- if(length(custom_materials) > 1)
- . += EXAMINE_SECTION_BREAK //SKYRAT EDIT ADDITION
- //SKYRAT EDIT ADDITION END
-
- var/list/materials_list = list()
- for(var/custom_material in custom_materials)
- var/datum/material/current_material = GET_MATERIAL_REF(custom_material)
- materials_list += "[current_material.name]"
- . += "It is made out of [english_list(materials_list)]."
-
- // SKYRAT EDIT ADDITION BEGIN - HR sections
- if(length(custom_materials) > 1)
- . += EXAMINE_SECTION_BREAK //SKYRAT EDIT ADDITION
- //SKYRAT EDIT ADDITION END
- if(reagents)
- var/user_sees_reagents = user.can_see_reagents()
- var/reagent_sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_REAGENT_EXAMINE, user, ., user_sees_reagents)
- if(!(reagent_sigreturn & STOP_GENERIC_REAGENT_EXAMINE))
- if(reagents.flags & TRANSPARENT)
- if(reagents.total_volume)
- . += "It contains [reagents.total_volume] units of various reagents[user_sees_reagents ? ":" : "."]"
- if(user_sees_reagents) //Show each individual reagent for detailed examination
- for(var/datum/reagent/current_reagent as anything in reagents.reagent_list)
- . += "• [round(current_reagent.volume, CHEMICAL_VOLUME_ROUNDING)] units of [current_reagent.name]"
- if(reagents.is_reacting)
- . += span_warning("It is currently reacting!")
- . += span_notice("The solution's pH is [round(reagents.ph, 0.01)] and has a temperature of [reagents.chem_temp]K.")
-
- else
- . += "It contains: Nothing."
- else if(reagents.flags & AMOUNT_VISIBLE)
- if(reagents.total_volume)
- . += span_notice("It has [reagents.total_volume] unit\s left.")
- else
- . += span_danger("It's empty.")
-
- SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .)
-
-/**
- * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_WINDOW (default 1 second)
- *
- * This is where you can put extra information on something that may be superfluous or not important in critical gameplay
- * moments, while allowing people to manually double-examine to take a closer look
- *
- * Produces a signal [COMSIG_ATOM_EXAMINE_MORE]
- */
-/atom/proc/examine_more(mob/user)
- SHOULD_CALL_PARENT(TRUE)
- RETURN_TYPE(/list)
-
- . = list()
- SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE_MORE, user, .)
- SEND_SIGNAL(user, COMSIG_MOB_EXAMINING_MORE, src, .)
-
-/// Wrapper for _update_appearance that is only called when APPEARANCE_SUCCESS_TRACKING is defined
-#ifdef APPEARANCE_SUCCESS_TRACKING
-/atom/proc/wrap_update_appearance(file, line, updates)
- INIT_COST_STATIC()
- EXPORT_STATS_TO_CSV_LATER("appearance_efficency.csv", _costs, _counting)
- var/old_appearance = appearance
-
- _update_appearance(updates)
- // We're checking to see if update_appearance DID anything to our appearance
- // If it didn't, or it produced the same thing, we'll mark it as such so it can potentially be opitmized
- if(old_appearance == appearance)
- SET_COST("SAME [file] [line]")
- return FALSE
- else
- SET_COST("DIFFERENT [file] [line]")
- return TRUE
-#endif
-
-/**
- * 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)
-
- . = NONE
- 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)
- SHOULD_CALL_PARENT(TRUE)
-
- . = NONE
- updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates)
- if(updates & UPDATE_ICON_STATE)
- update_icon_state()
- . |= UPDATE_ICON_STATE
-
- if(updates & UPDATE_OVERLAYS)
- if(LAZYLEN(managed_vis_overlays))
- SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
-
- var/list/new_overlays = update_overlays(updates)
- var/nulls = 0
- for(var/i in 1 to length(new_overlays))
- var/atom/maybe_not_an_atom = new_overlays[i]
- if(isnull(maybe_not_an_atom))
- nulls++
- continue
- if(istext(maybe_not_an_atom) || isicon(maybe_not_an_atom))
- continue
- new_overlays[i] = maybe_not_an_atom.appearance
- if(nulls)
- for(var/i in 1 to nulls)
- new_overlays -= null
-
- var/identical = FALSE
- var/new_length = length(new_overlays)
- if(!managed_overlays && !new_length)
- identical = TRUE
- else if(!islist(managed_overlays))
- if(new_length == 1 && managed_overlays == new_overlays[1])
- identical = TRUE
- else if(length(managed_overlays) == new_length)
- identical = TRUE
- for(var/i in 1 to length(managed_overlays))
- if(managed_overlays[i] != new_overlays[i])
- identical = FALSE
- break
-
- if(!identical)
- var/full_control = FALSE
- if(managed_overlays)
- full_control = length(overlays) == (islist(managed_overlays) ? length(managed_overlays) : 1)
- if(full_control)
- overlays = null
- else
- cut_overlay(managed_overlays)
-
- switch(length(new_overlays))
- if(0)
- if(full_control)
- POST_OVERLAY_CHANGE(src)
- managed_overlays = null
- if(1)
- add_overlay(new_overlays)
- managed_overlays = new_overlays[1]
- else
- add_overlay(new_overlays)
- managed_overlays = new_overlays
-
- . |= UPDATE_OVERLAYS
-
- . |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
-
-/// Updates the icon state of the atom
-/atom/proc/update_icon_state()
- SHOULD_CALL_PARENT(TRUE)
- return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
-
-/// Updates the overlays of the atom
-/atom/proc/update_overlays()
- SHOULD_CALL_PARENT(TRUE)
- . = list()
- SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
-
-/**
- * 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)
-
-/// Handles updates to greyscale value updates.
-/// The colors argument can be either a list or the full color string.
-/// Child procs should call parent last so the update happens after all changes.
-/atom/proc/set_greyscale(list/colors, new_config)
- SHOULD_CALL_PARENT(TRUE)
- if(istype(colors))
- colors = colors.Join("")
- if(!isnull(colors) && greyscale_colors != colors) // If you want to disable greyscale stuff then give a blank string
- greyscale_colors = colors
-
- if(!isnull(new_config) && greyscale_config != new_config)
- greyscale_config = new_config
-
- update_greyscale()
-
-/// Checks if this atom uses the GAGS system and if so updates the icon
-/atom/proc/update_greyscale()
- SHOULD_CALL_PARENT(TRUE)
- if(greyscale_colors && greyscale_config)
- icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)
- if(!smoothing_flags) // This is a bitfield but we're just checking that some sort of smoothing is happening
- return
- update_atom_colour()
- QUEUE_SMOOTH(src)
-
-/**
- * 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)
-
-/// Handle what happens when your contents are exploded by a bomb
-/atom/proc/contents_explosion(severity, target)
- return //For handling the effects of explosions on contents that would not normally be effected
-
-/**
- * React to being hit by an explosion
- *
- * Should be called through the [EX_ACT] wrapper macro.
- * The wrapper takes care of the [COMSIG_ATOM_EX_ACT] signal.
- * as well as calling [/atom/proc/contents_explosion].
- *
- * Returns TRUE by default, and behavior should be implemented on children procs on a per-atom basis. Should only return FALSE if we resist the explosion for any reason.
- * We assume that the default is TRUE because all atoms should be considered destructible in some manner unless they explicitly opt out (in our current framework).
- * However, the return value itself doesn't have any external consumers, it's only so children procs can listen to the value from their parent procs (due to the nature of the [EX_ACT] macro).
- * Thus, the return value only matters on overrides of this proc, and the only thing that truly matters is the code that is executed (applying damage, calling damage procs, etc.)
- *
- */
-/atom/proc/ex_act(severity, target)
- set waitfor = FALSE
- return TRUE
-
-/**
- * React to a hit by a blob objecd
- *
- * default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal
- */
-/atom/proc/blob_act(obj/structure/blob/attacking_blob)
- var/blob_act_result = SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, attacking_blob)
- if (blob_act_result & COMPONENT_CANCEL_BLOB_ACT)
- return FALSE
- return TRUE
-
-/**
- * Respond to fire being used on our atom
- *
- * Default behaviour is to send [COMSIG_ATOM_FIRE_ACT] and return
- */
-/atom/proc/fire_act(exposed_temperature, exposed_volume)
- SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
- return FALSE
-
-/**
- * Sends [COMSIG_ATOM_EXTINGUISH] signal, which properly removes burning component if it is present.
- *
- * Default behaviour is to send [COMSIG_ATOM_ACID_ACT] and return
- */
-/atom/proc/extinguish()
- SHOULD_CALL_PARENT(TRUE)
- return SEND_SIGNAL(src, COMSIG_ATOM_EXTINGUISH)
-
-/**
- * React to being hit by a thrown object
- *
- * Default behaviour is to call [hitby_react][/atom/proc/hitby_react] on ourselves after 2 seconds if we are dense
- * and under normal gravity.
- *
- * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is
- * deleted shortly after hitting something (during explosions or other massive events that
- * throw lots of items around - singularity being a notable example)
- */
-/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)
-
-/**
- * We have have actually hit the passed in atom
- *
- * Default behaviour is to move back from the item that hit us
- */
-/atom/proc/hitby_react(atom/movable/harmed_atom)
- if(harmed_atom && isturf(harmed_atom.loc))
- step(harmed_atom, REVERSE_DIR(harmed_atom.dir))
-
-///Handle the atom being slipped over
-/atom/proc/handle_slip(mob/living/carbon/slipped_carbon, knockdown_amount, obj/slipping_object, lube, paralyze, force_drop)
- return
-
-///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() != /datum/reagent/blood)
- return
- return list("ANIMAL DNA" = "Y-")
-
-///Get the mobs dna list
-/mob/living/carbon/get_blood_dna_list()
- if(get_blood_id() != /datum/reagent/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*")
-
-/mob/living/silicon/get_blood_dna_list()
- return
-
-///to add a mob's dna info into an object's blood_dna list.
-/atom/proc/transfer_mob_blood_dna(mob/living/injected_mob)
- // Returns 0 if we have that blood already
- var/new_blood_dna = injected_mob.get_blood_dna_list()
- if(!new_blood_dna)
- return FALSE
- var/old_length = GET_ATOM_BLOOD_DNA_LENGTH(src)
- add_blood_DNA(new_blood_dna)
- if(GET_ATOM_BLOOD_DNA_LENGTH(src) == old_length)
- return FALSE
- return TRUE
-
-///to add blood from a mob onto something, and transfer their dna info
-/atom/proc/add_mob_blood(mob/living/injected_mob)
- var/list/blood_dna = injected_mob.get_blood_dna_list()
- if(!blood_dna)
- return FALSE
- return add_blood_DNA(blood_dna)
-
-///Is this atom in space
-/atom/proc/isinspace()
- if(isspaceturf(get_turf(src)))
- return TRUE
- else
- return FALSE
-
-///Used for making a sound when a mob involuntarily falls into the ground.
-/atom/proc/handle_fall(mob/faller)
- return
-
-///Respond to the singularity eating this atom
-/atom/proc/singularity_act()
- return
-
-/**
- * Respond to the singularity pulling on us
- *
- * Default behaviour is to send [COMSIG_ATOM_SING_PULL] and return
- */
-/atom/proc/singularity_pull(obj/singularity/singularity, current_size)
- SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, singularity, 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)
- return FALSE
-
-/**
- * Respond to an emag being used on our atom
- *
- * Args:
- * * mob/user: The mob that used the emag. Nullable.
- * * obj/item/card/emag/emag_card: The emag that was used. Nullable.
- *
- * Returns:
- * TRUE if the emag had any effect, falsey otherwise.
- */
-/atom/proc/emag_act(mob/user, obj/item/card/emag/emag_card)
- return (SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user, emag_card))
-
-/**
- * Respond to narsie eating our atom
- *
- * Default behaviour is to send [COMSIG_ATOM_NARSIE_ACT] and return
- */
-/atom/proc/narsie_act()
- SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT)
-
-
-///Return the values you get when an RCD eats you?
-/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- return FALSE
-
-
-/**
- * Respond to an RCD acting on our item
- *
- * Default behaviour is to send [COMSIG_ATOM_RCD_ACT] and return FALSE
- */
-/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data)
- SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, rcd_data["[RCD_DESIGN_MODE]"])
- return FALSE
-
-/**
- * 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
-
-/**
- * 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)
- dir = newdir
- SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, dir, newdir)
- if(smoothing_flags & SMOOTH_BORDER_OBJECT)
- QUEUE_SMOOTH_NEIGHBORS(src)
-
-/**
- * Called when the atom log's in or out
- *
- * Default behaviour is to call on_log on the location this atom is in
- */
-/atom/proc/on_log(login)
- if(loc)
- loc.on_log(login)
-
-
-/*
- Atom Colour Priority System
- A System that gives finer control over which atom colour to colour the atom with.
- The "highest priority" one is always displayed as opposed to the default of
- "whichever was set last is displayed"
-*/
-
-
-///Adds an instance of colour_type to the atom's atom_colours list
-/atom/proc/add_atom_colour(coloration, colour_priority)
- if(!atom_colours || !atom_colours.len)
- atom_colours = list()
- atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
- if(!coloration)
- return
- if(colour_priority > atom_colours.len)
- 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)
- return
- if(colour_priority > atom_colours.len)
- 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()
-
-/**
- * Checks if this atom has the passed color
- * Can optionally be supplied with a range of priorities, IE only checking "washable" or above
- */
-/atom/proc/is_atom_colour(looking_for_color, min_priority_index = 1, max_priority_index = COLOUR_PRIORITY_AMOUNT)
- // make sure uppertext hex strings don't mess with lowertext hex strings
- looking_for_color = lowertext(looking_for_color)
-
- if(!LAZYLEN(atom_colours))
- // no atom colors list has been set up, just check the color var
- return lowertext(color) == looking_for_color
-
- for(var/i in min_priority_index to max_priority_index)
- if(lowertext(atom_colours[i]) == looking_for_color)
- return TRUE
-
- return FALSE
-
-///Resets the atom's color to null, and then sets it to the highest priority colour available
-/atom/proc/update_atom_colour()
- color = null
- if(!atom_colours)
- return
- for(var/checked_color in atom_colours)
- if(islist(checked_color))
- var/list/color_list = checked_color
- if(color_list.len)
- color = color_list
- return
- else if(checked_color)
- color = checked_color
- return
-
-
-/**
- * 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
- */
-/atom/proc/wash(clean_types)
- SHOULD_CALL_PARENT(TRUE)
- if(SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, clean_types) & COMPONENT_CLEANED)
- return TRUE
-
- // Basically "if has washable coloration"
- if(length(atom_colours) >= WASHABLE_COLOUR_PRIORITY && atom_colours[WASHABLE_COLOUR_PRIORITY])
- remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- return TRUE
- return FALSE
-
-/**
- * call back when a var is edited on this atom
- *
- * Can be used to implement special handling of vars
- *
- * At the atom level, if you edit a var named "color" it will add the atom colour with
- * admin level priority to the atom colours list
- *
- * Also, if GLOB.Debug2 is FALSE, it sets the [ADMIN_SPAWNED_1] flag on [flags_1][/atom/var/flags_1], which signifies
- * the object has been admin edited
- */
-/atom/vv_edit_var(var_name, var_value)
- var/old_light_flags = light_flags
- // Disable frozen lights for now, so we can actually modify it
- light_flags &= ~LIGHT_FROZEN
- switch(var_name)
- if(NAMEOF(src, light_range))
- if(light_system == STATIC_LIGHT)
- set_light(l_range = var_value)
- else
- set_light_range(var_value)
- . = TRUE
- if(NAMEOF(src, light_power))
- if(light_system == STATIC_LIGHT)
- set_light(l_power = var_value)
- else
- set_light_power(var_value)
- . = TRUE
- if(NAMEOF(src, light_color))
- if(light_system == STATIC_LIGHT)
- set_light(l_color = var_value)
- else
- set_light_color(var_value)
- . = TRUE
- if(NAMEOF(src, light_angle))
- if(light_system == STATIC_LIGHT)
- set_light(l_angle = var_value)
- . = TRUE
- if(NAMEOF(src, light_dir))
- if(light_system == STATIC_LIGHT)
- set_light(l_dir = var_value)
- . = TRUE
- if(NAMEOF(src, light_height))
- if(light_system == STATIC_LIGHT)
- set_light(l_height = var_value)
- . = TRUE
- if(NAMEOF(src, light_on))
- if(light_system == STATIC_LIGHT)
- set_light(l_on = var_value)
- else
- set_light_on(var_value)
- . = TRUE
- if(NAMEOF(src, light_flags))
- set_light_flags(var_value)
- // I'm sorry
- old_light_flags = var_value
- . = TRUE
- if(NAMEOF(src, smoothing_junction))
- set_smoothed_icon_state(var_value)
- . = TRUE
- if(NAMEOF(src, opacity))
- set_opacity(var_value)
- . = TRUE
- if(NAMEOF(src, base_pixel_x))
- set_base_pixel_x(var_value)
- . = TRUE
- if(NAMEOF(src, base_pixel_y))
- set_base_pixel_y(var_value)
- . = TRUE
-
- light_flags = old_light_flags
- if(!isnull(.))
- datum_flags |= DF_VAR_EDITED
- return
-
- if(!GLOB.Debug2)
- flags_1 |= ADMIN_SPAWNED_1
-
- . = ..()
-
- switch(var_name)
- if(NAMEOF(src, color))
- add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
- update_appearance()
-
-
-/**
- * Return the markup to for the dropdown list for the VV panel for this atom
- *
- * Override in subtypes to add custom VV handling in the VV panel
- */
-/atom/vv_get_dropdown()
- . = ..()
- VV_DROPDOWN_OPTION("", "---------")
- if(!ismovable(src))
- var/turf/curturf = get_turf(src)
- if(curturf)
- . += ""
- VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRANSFORM, "Modify Transform")
- VV_DROPDOWN_OPTION(VV_HK_SPIN_ANIMATION, "SpinAnimation")
- VV_DROPDOWN_OPTION(VV_HK_STOP_ALL_ANIMATIONS, "Stop All Animations")
- VV_DROPDOWN_OPTION(VV_HK_SHOW_HIDDENPRINTS, "Show Hiddenprint log")
- VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent")
- VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
- VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
- VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
- VV_DROPDOWN_OPTION(VV_HK_EDIT_COLOR_MATRIX, "Edit Color as Matrix")
- VV_DROPDOWN_OPTION(VV_HK_TEST_MATRIXES, "Test Matrices")
- VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
- VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify Armor")
- if(greyscale_colors)
- VV_DROPDOWN_OPTION(VV_HK_MODIFY_GREYSCALE, "Modify greyscale colors")
-
-/atom/vv_do_topic(list/href_list)
- . = ..()
- if(href_list[VV_HK_ADD_REAGENT] && check_rights(R_VAREDIT))
- if(!reagents)
- var/amount = input(usr, "Specify the reagent size of [src]", "Set Reagent Size", 50) as num|null
- if(amount)
- create_reagents(amount)
-
- if(reagents)
- var/chosen_id
- switch(tgui_alert(usr, "Choose a method.", "Add Reagents", list("Search", "Choose from a list", "I'm feeling lucky")))
- if("Search")
- var/valid_id
- while(!valid_id)
- chosen_id = input(usr, "Enter the ID of the reagent you want to add.", "Search reagents") as null|text
- if(isnull(chosen_id)) //Get me out of here!
- break
- if (!ispath(text2path(chosen_id)))
- chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/datum/reagent)))
- if (ispath(chosen_id))
- valid_id = TRUE
- else
- valid_id = TRUE
- if(!valid_id)
- to_chat(usr, span_warning("A reagent with that ID doesn't exist!"))
- if("Choose from a list")
- chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in sort_list(subtypesof(/datum/reagent), GLOBAL_PROC_REF(cmp_typepaths_asc))
- if("I'm feeling lucky")
- chosen_id = pick(subtypesof(/datum/reagent))
- if(chosen_id)
- var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", reagents.maximum_volume) as num|null
- if(amount)
- reagents.add_reagent(chosen_id, amount)
- log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to [src]")
- message_admins(span_notice("[key_name(usr)] has added [amount] units of [chosen_id] to [src]"))
-
- if(href_list[VV_HK_TRIGGER_EXPLOSION] && check_rights(R_FUN))
- usr.client.cmd_admin_explosion(src)
-
- if(href_list[VV_HK_TRIGGER_EMP] && check_rights(R_FUN))
- usr.client.cmd_admin_emp(src)
-
- if(href_list[VV_HK_SHOW_HIDDENPRINTS] && check_rights(R_ADMIN))
- usr.client.cmd_show_hiddenprints(src)
-
- if(href_list[VV_HK_ARMOR_MOD])
- var/list/pickerlist = list()
- var/list/armorlist = get_armor().get_rating_list()
-
- for (var/i in armorlist)
- pickerlist += list(list("value" = armorlist[i], "name" = i))
-
- var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [src]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist)
- var/list/armor_all = ARMOR_LIST_ALL()
-
- if (islist(result))
- if (result["button"] != 2) // If the user pressed the cancel button
- // text2num conveniently returns a null on invalid values
- var/list/converted = list()
- for(var/armor_key in armor_all)
- converted[armor_key] = text2num(result["values"][armor_key])
- set_armor(get_armor().generate_new_with_specific(converted))
- var/message = "[key_name(usr)] modified the armor on [src] ([type]) to: "
- for(var/armor_key in armor_all)
- message += "[armor_key]=[get_armor_rating(armor_key)],"
- message = copytext(message, 1, -1)
- log_admin(span_notice(message))
- message_admins(span_notice(message))
-
- if(href_list[VV_HK_ADD_AI])
- if(!check_rights(R_VAREDIT))
- return
- var/result = input(usr, "Choose the AI controller to apply to this atom WARNING: Not all AI works on all atoms.", "AI controller") as null|anything in subtypesof(/datum/ai_controller)
- if(!result)
- return
- ai_controller = new result(src)
-
- if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT))
- var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate","Shear")
- var/matrix/M = transform
- if(!result)
- return
- switch(result)
- if("Scale")
- var/x = input(usr, "Choose x mod","Transform Mod") as null|num
- var/y = input(usr, "Choose y mod","Transform Mod") as null|num
- if(isnull(x) || isnull(y))
- return
- transform = M.Scale(x,y)
- if("Translate")
- var/x = input(usr, "Choose x mod (negative = left, positive = right)","Transform Mod") as null|num
- var/y = input(usr, "Choose y mod (negative = down, positive = up)","Transform Mod") as null|num
- if(isnull(x) || isnull(y))
- return
- transform = M.Translate(x,y)
- if("Shear")
- var/x = input(usr, "Choose x mod","Transform Mod") as null|num
- var/y = input(usr, "Choose y mod","Transform Mod") as null|num
- if(isnull(x) || isnull(y))
- return
- transform = M.Shear(x,y)
- if("Rotate")
- var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num
- if(isnull(angle))
- return
- transform = M.Turn(angle)
-
- SEND_SIGNAL(src, COMSIG_ATOM_VV_MODIFY_TRANSFORM)
-
- if(href_list[VV_HK_SPIN_ANIMATION] && check_rights(R_VAREDIT))
- var/num_spins = input(usr, "Do you want infinite spins?", "Spin Animation") in list("Yes", "No")
- if(num_spins == "No")
- num_spins = input(usr, "How many spins?", "Spin Animation") as null|num
- else
- num_spins = -1
- if(!num_spins)
- return
- var/spin_speed = input(usr, "How fast?", "Spin Animation") as null|num
- if(!spin_speed)
- return
- var/direction = input(usr, "Which direction?", "Spin Animation") in list("Clockwise", "Counter-clockwise")
- switch(direction)
- if("Clockwise")
- direction = 1
- if("Counter-clockwise")
- direction = 0
- else
- return
- SpinAnimation(spin_speed, num_spins, direction)
-
- if(href_list[VV_HK_STOP_ALL_ANIMATIONS] && check_rights(R_VAREDIT))
- var/result = input(usr, "Are you sure?", "Stop Animating") in list("Yes", "No")
- if(result == "Yes")
- animate(src, transform = null, flags = ANIMATION_END_NOW) // Literally just fucking stop animating entirely because admin said so
- return
-
- if(href_list[VV_HK_AUTO_RENAME] && check_rights(R_VAREDIT))
- var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
- // Check the new name against the chat filter. If it triggers the IC chat filter, give an option to confirm.
- if(newname && !(is_ic_filtered(newname) || is_soft_ic_filtered(newname) && tgui_alert(usr, "Your selected name contains words restricted by IC chat filters. Confirm this new name?", "IC Chat Filter Conflict", list("Confirm", "Cancel")) != "Confirm"))
- vv_auto_rename(newname)
-
- if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
- usr.client?.open_filter_editor(src)
-
- if(href_list[VV_HK_EDIT_COLOR_MATRIX] && check_rights(R_VAREDIT))
- usr.client?.open_color_matrix_editor(src)
-
- if(href_list[VV_HK_TEST_MATRIXES] && check_rights(R_VAREDIT))
- usr.client?.open_matrix_tester(src)
-
-/atom/vv_get_header()
- . = ..()
- var/refid = REF(src)
- . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]"
- . += " <<[dir2text(dir) || dir]>>"
-
-///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()
-
-/atom/proc/vv_auto_rename(newname)
- name = newname
-
-/**
- * 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)
-
-///Return atom temperature
-/atom/proc/return_temperature()
- return
-
-/**
- *Tool behavior procedure. Redirects to tool-specific procs by default.
- *
- * You can override it to catch all tool interactions, for use in complex deconstruction procs.
- *
- * Must return parent proc ..() in the end if overridden
- */
-/atom/proc/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
- var/act_result
- var/signal_result
-
- var/is_left_clicking = !is_right_clicking
-
- if(is_left_clicking) // Left click first for sensibility
- var/list/processing_recipes = list() //List of recipes that can be mutated by sending the signal
- signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, tool, processing_recipes)
- if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
- return TOOL_ACT_SIGNAL_BLOCKING
- if(processing_recipes.len)
- process_recipes(user, tool, processing_recipes)
- if(QDELETED(tool))
- return TRUE
- else
- signal_result = SEND_SIGNAL(src, COMSIG_ATOM_SECONDARY_TOOL_ACT(tool_type), user, tool)
- if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
- return TOOL_ACT_SIGNAL_BLOCKING
-
- switch(tool_type)
- if(TOOL_CROWBAR)
- act_result = is_left_clicking ? crowbar_act(user, tool) : crowbar_act_secondary(user, tool)
- if(TOOL_MULTITOOL)
- act_result = is_left_clicking ? multitool_act(user, tool) : multitool_act_secondary(user, tool)
- if(TOOL_SCREWDRIVER)
- act_result = is_left_clicking ? screwdriver_act(user, tool) : screwdriver_act_secondary(user, tool)
- if(TOOL_WRENCH)
- act_result = is_left_clicking ? wrench_act(user, tool) : wrench_act_secondary(user, tool)
- if(TOOL_WIRECUTTER)
- act_result = is_left_clicking ? wirecutter_act(user, tool) : wirecutter_act_secondary(user, tool)
- if(TOOL_WELDER)
- act_result = is_left_clicking ? welder_act(user, tool) : welder_act_secondary(user, tool)
- if(TOOL_ANALYZER)
- act_result = is_left_clicking ? analyzer_act(user, tool) : analyzer_act_secondary(user, tool)
- if(!act_result)
- return
-
- // A tooltype_act has completed successfully
- if(is_left_clicking)
- log_tool("[key_name(user)] used [tool] on [src] at [AREACOORD(src)]")
- SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_PRIMARY(tool_type), src)
- else
- log_tool("[key_name(user)] used [tool] on [src] (right click) at [AREACOORD(src)]")
- SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_SECONDARY(tool_type), src)
- return TOOL_ACT_TOOLTYPE_SUCCESS
-
-
-/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]
- to_chat(user, span_notice("You start working on [src]."))
- if(process_item.use_tool(src, user, processing_time, volume=50))
- 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())
- 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_atom.OnCreatedFromProcessing(user, process_item, chosen_option, src)
- 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)
- return
-
-/atom/proc/UsedforProcessing(mob/living/user, obj/item/used_item, list/chosen_option)
- qdel(src)
- return
-
-/atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/work_tool, list/chosen_option, atom/original_atom)
- SHOULD_CALL_PARENT(TRUE)
-
- SEND_SIGNAL(src, COMSIG_ATOM_CREATEDBY_PROCESSING, original_atom, chosen_option)
- if(user.mind)
- ADD_TRAIT(src, TRAIT_FOOD_CHEF_MADE, REF(user.mind))
-
-//! Tool-specific behavior procs.
-///
-
-/// Called on an object when a tool with crowbar capabilities is used to left click an object
-/atom/proc/crowbar_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with crowbar capabilities is used to right click an object
-/atom/proc/crowbar_act_secondary(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with multitool capabilities is used to left click an object
-/atom/proc/multitool_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with multitool capabilities is used to right click an object
-/atom/proc/multitool_act_secondary(mob/living/user, obj/item/tool)
- return
-
-///Check if an item supports a data buffer (is a multitool)
-/atom/proc/multitool_check_buffer(user, obj/item/multitool, silent = FALSE)
- if(!istype(multitool, /obj/item/multitool))
- if(user && !silent)
- to_chat(user, span_warning("[multitool] has no data buffer!"))
- return FALSE
- return TRUE
-
-/// Called on an object when a tool with screwdriver capabilities is used to left click an object
-/atom/proc/screwdriver_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with screwdriver capabilities is used to right click an object
-/atom/proc/screwdriver_act_secondary(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with wrench capabilities is used to left click an object
-/atom/proc/wrench_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with wrench capabilities is used to right click an object
-/atom/proc/wrench_act_secondary(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with wirecutter capabilities is used to left click an object
-/atom/proc/wirecutter_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with wirecutter capabilities is used to right click an object
-/atom/proc/wirecutter_act_secondary(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with welder capabilities is used to left click an object
-/atom/proc/welder_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with welder capabilities is used to right click an object
-/atom/proc/welder_act_secondary(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with analyzer capabilities is used to left click an object
-/atom/proc/analyzer_act(mob/living/user, obj/item/tool)
- return
-
-/// Called on an object when a tool with analyzer capabilities is used to right click an object
-/atom/proc/analyzer_act_secondary(mob/living/user, obj/item/tool)
- return
-
-///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)
-
-/// Sets the custom materials for an item.
-/atom/proc/set_custom_materials(list/materials, multiplier = 1)
- if(custom_materials && material_flags & MATERIAL_EFFECTS) //Only runs if custom materials existed at first and affected src.
- for(var/current_material in custom_materials)
- var/datum/material/custom_material = GET_MATERIAL_REF(current_material)
- custom_material.on_removed(src, custom_materials[current_material] * material_modifier, material_flags) //Remove the current materials
-
- if(!length(materials))
- custom_materials = null
- return
-
- if(material_flags & MATERIAL_EFFECTS)
- for(var/current_material in materials)
- var/datum/material/custom_material = GET_MATERIAL_REF(current_material)
- custom_material.on_applied(src, materials[current_material] * multiplier * material_modifier, material_flags)
-
- custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier)
-
-/**
- * Returns the material composition of the atom.
- *
- * Used when recycling items, specifically to turn alloys back into their component mats.
- *
- * Exists because I'd need to add a way to un-alloy alloys or otherwise deal
- * with people converting the entire stations material supply into alloys.
- *
- * Arguments:
- * - flags: A set of flags determining how exactly the materials are broken down.
- */
-/atom/proc/get_material_composition(breakdown_flags=NONE)
- . = list()
- if(!(breakdown_flags & BREAKDOWN_INCLUDE_ALCHEMY) && HAS_TRAIT(src, TRAIT_MAT_TRANSMUTED))
- return
-
- var/list/cached_materials = custom_materials
- for(var/mat in cached_materials)
- var/datum/material/material = GET_MATERIAL_REF(mat)
- var/list/material_comp = material.return_composition(cached_materials[mat], breakdown_flags)
- for(var/comp_mat in material_comp)
- .[comp_mat] += material_comp[comp_mat]
-
-/**
- * Fetches a list of all of the materials this object has of the desired type. Returns null if there is no valid materials of the type
- *
- * Arguments:
- * - [required_material][/datum/material]: The type of material we are checking for
- * - mat_amount: The minimum required amount of material
- */
-/atom/proc/has_material_type(datum/material/required_material, mat_amount = 0)
- var/list/cached_materials = custom_materials
- if(!length(cached_materials))
- return null
-
- var/materials_of_type
- for(var/current_material in cached_materials)
- if(cached_materials[current_material] < mat_amount)
- continue
- var/datum/material/material = GET_MATERIAL_REF(current_material)
- if(!istype(material, required_material))
- continue
- LAZYSET(materials_of_type, material, cached_materials[current_material])
-
- return materials_of_type
-
-/**
- * Fetches a list of all of the materials this object has with the desired material category.
- *
- * Arguments:
- * - category: The category to check for
- * - any_flags: Any bitflags that must be present for the category
- * - all_flags: All bitflags that must be present for the category
- * - no_flags: Any bitflags that must not be present for the category
- * - mat_amount: The minimum amount of materials that must be present
- */
-/atom/proc/has_material_category(category, any_flags=0, all_flags=0, no_flags=0, mat_amount=0)
- var/list/cached_materials = custom_materials
- if(!length(cached_materials))
- return null
-
- var/materials_of_category
- for(var/current_material in cached_materials)
- if(cached_materials[current_material] < mat_amount)
- continue
- var/datum/material/material = GET_MATERIAL_REF(current_material)
- var/category_flags = material?.categories[category]
- if(isnull(category_flags))
- continue
- if(any_flags && !(category_flags & any_flags))
- continue
- if(all_flags && (all_flags != (category_flags & all_flags)))
- continue
- if(no_flags && (category_flags & no_flags))
- continue
- LAZYSET(materials_of_category, material, cached_materials[current_material])
- return materials_of_category
-
-/**
- * Gets the most common material in the object.
- */
-/atom/proc/get_master_material()
- var/list/cached_materials = custom_materials
- if(!length(cached_materials))
- return null
-
- var/most_common_material = null
- var/max_amount = 0
- for(var/material in cached_materials)
- if(cached_materials[material] > max_amount)
- most_common_material = material
- max_amount = cached_materials[material]
-
- if(most_common_material)
- return GET_MATERIAL_REF(most_common_material)
-
-/**
- * Gets the total amount of materials in this atom.
- */
-/atom/proc/get_custom_material_amount()
- return isnull(custom_materials) ? 0 : counterlist_sum(custom_materials)
-
-
-///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
-
-
-///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
- * * 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 && (SSmapping.gravity_by_z_level[gravity_turf.z] || turf_area.has_gravity)
-
-/**
- * Causes effects when the atom gets hit by a rust effect from heretics
- *
- * Override this if you want custom behaviour in whatever gets hit by the rust
- */
-/atom/proc/rust_heretic_act()
- return
-
-/**
- * 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 when something resists while this atom is its loc
-/atom/proc/container_resist_act(mob/living/user)
-
-/**
- * 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
-
-/**
- * Recursive getter method to return a list of all ghosts orbitting this atom
- *
- * This will work fine without manually passing arguments.
- * * processed - The list of atoms we've already convered
- * * source - Is this the atom for who we're counting up all the orbiters?
- * * ignored_stealthed_admins - If TRUE, don't count admins who are stealthmoded and orbiting this
- */
-/atom/proc/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
- var/list/output = list()
- if(!processed)
- processed = list()
- else if(src in processed)
- return output
-
- if(!source)
- output += src
-
- processed += src
- for(var/atom/atom_orbiter as anything in orbiters?.orbiter_list)
- output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
- return output
-
-/mob/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
- if(!source && ignore_stealthed_admins && client?.holder?.fakekey)
- return list()
- return ..()
-
-/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
-
- ///SKYRAT EDIT ADDITION BEGIN
- // Face directions on combat mode. No procs, no typechecks, just a var for speed
- if(user.face_mouse)
- user.face_atom(src)
- ///SKYRAT EDIT ADDITION END
-
- // 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
-
- active_hud.screentip_text.maptext_y = 10 // 10px lines us up with the action buttons top left corner
- 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 = ""
-
- if (isliving(user) || isovermind(user) || isaicamera(user))
- var/obj/item/held_item = user.get_active_held_item()
-
- 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]"
- //first extra line pushes atom name line up 11px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
- active_hud.screentip_text.maptext_y = -1 + (extra_lines - 1) * -9
-
- if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
- active_hud.screentip_text.maptext = ""
- else
- //We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
- active_hud.screentip_text.maptext = "[name][extra_context]"
-
-/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
-/atom/proc/GetMergeGroup(id, list/allowed_types)
- RETURN_TYPE(/datum/merger)
- var/datum/merger/candidate
- if(mergers)
- candidate = mergers[id]
- if(!candidate)
- new /datum/merger(id, allowed_types, src)
- candidate = mergers[id]
- return candidate
-
-/**
- * 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
-
-/// Makes this atom look like a "hologram"
-/// So transparent, blue, with a scanline and an emissive glow
-/// This is acomplished using a combination of filters and render steps/overlays
-/// The degree of the opacity is optional, based off the opacity arg (0 -> 1)
-/atom/proc/makeHologram(opacity = 0.5)
- // First, we'll make things blue (roughly) and sorta transparent
- add_filter("HOLO: Color and Transparent", 1, color_matrix_filter(rgb(125,180,225, opacity * 255)))
- // Now we're gonna do a scanline effect
- // Gonna take this atom and give it a render target, then use it as a source for a filter
- // (We use an atom because it seems as if setting render_target on an MA is just invalid. I hate this engine)
- var/atom/movable/scanline = new(null)
- scanline.icon = 'icons/effects/effects.dmi'
- scanline.icon_state = "scanline"
- scanline.appearance_flags |= RESET_TRANSFORM
- // * so it doesn't render
- var/static/uid_scan = 0
- scanline.render_target = "*HoloScanline [uid_scan]"
- uid_scan++
- // Now we add it as a filter, and overlay the appearance so the render source is always around
- add_filter("HOLO: Scanline", 2, alpha_mask_filter(render_source = scanline.render_target))
- add_overlay(scanline)
- qdel(scanline)
- // Annd let's make the sucker emissive, so it glows in the dark
- if(!render_target)
- var/static/uid = 0
- render_target = "HOLOGRAM [uid]"
- uid++
- // I'm using static here to reduce the overhead, it does mean we need to do plane stuff manually tho
- var/static/atom/movable/render_step/emissive/glow = new(null)
- glow.render_source = render_target
- SET_PLANE_EXPLICIT(glow, initial(glow.plane), src)
- // We're creating a render step that copies ourselves, and draws it to the emissive plane
- // Then we overlay it, and release "ownership" back to this proc, since we get to keep the appearance it generates
- // We can't just use an MA from the start cause render_source setting starts going fuckey REALLY quick
- var/mutable_appearance/glow_appearance = new(glow)
- add_overlay(glow_appearance)
- LAZYADD(update_overlays_on_z, glow_appearance)
-
-/**
- * Proc called when you want the atom to spin around the center of its icon (or where it would be if its transform var is translated)
- * By default, it makes the atom spin forever and ever at a speed of 60 rpm.
- *
- * Arguments:
- * * speed: how much it takes for the atom to complete one 360° rotation
- * * loops: how many times do we want the atom to rotate
- * * clockwise: whether the atom ought to spin clockwise or counter-clockwise
- * * segments: in how many animate calls the rotation is split. Probably unnecessary, but you shouldn't set it lower than 3 anyway.
- * * parallel: whether the animation calls have the ANIMATION_PARALLEL flag, necessary for it to run alongside concurrent animations.
- */
-/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = TRUE, segments = 3, parallel = TRUE)
- if(!segments)
- return
- var/segment = 360/segments
- if(!clockwise)
- segment = -segment
- SEND_SIGNAL(src, COMSIG_ATOM_SPIN_ANIMATION, speed, loops, segments, segment)
- do_spin_animation(speed, loops, segments, segment, parallel)
-
-#define INVISIBILITY_VALUE 1
-#define INVISIBILITY_PRIORITY 2
-
-/atom/proc/RecalculateInvisibility()
- PRIVATE_PROC(TRUE)
-
- if(!invisibility_sources)
- current_invisibility_priority = -INFINITY
- invisibility = initial(invisibility)
- return
-
- var/highest_priority
- var/list/highest_priority_invisibility_data
- for(var/entry in invisibility_sources)
- var/list/priority_data
- if(islist(entry))
- priority_data = entry
- else
- priority_data = invisibility_sources[entry]
-
- var/priority = priority_data[INVISIBILITY_PRIORITY]
- if(highest_priority > priority) // In the case of equal priorities, we use the last thing in the list so that more recent changes apply first
- continue
-
- highest_priority = priority
- highest_priority_invisibility_data = priority_data
-
- current_invisibility_priority = highest_priority
- invisibility = highest_priority_invisibility_data[INVISIBILITY_VALUE]
-
-/**
- * Sets invisibility according to priority.
- * If you want to be able to undo the value you set back to what it would be otherwise,
- * you should provide an id here and remove it using RemoveInvisibility(id)
- */
-/atom/proc/SetInvisibility(desired_value, id, priority=0)
- if(!invisibility_sources)
- invisibility_sources = list()
-
- if(id)
- invisibility_sources[id] = list(desired_value, priority)
- else
- invisibility_sources += list(list(desired_value, priority))
-
- if(current_invisibility_priority > priority)
- return
-
- RecalculateInvisibility()
-
-/// Removes the specified invisibility source from the tracker
-/atom/proc/RemoveInvisibility(id)
- if(!invisibility_sources)
- return
-
- var/list/priority_data = invisibility_sources[id]
- invisibility_sources -= id
-
- if(length(invisibility_sources) == 0)
- invisibility_sources = null
-
- if(current_invisibility_priority > priority_data[INVISIBILITY_PRIORITY])
- return
-
- RecalculateInvisibility()
-
-#undef INVISIBILITY_VALUE
-#undef INVISIBILITY_PRIORITY
diff --git a/tgstation.dme b/tgstation.dme
index 210224aa73c..e94859689d3 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -464,7 +464,6 @@
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\admin.dm"
#include "code\__HELPERS\ai.dm"
-#include "code\__HELPERS\animations.dm"
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\atmospherics.dm"
#include "code\__HELPERS\atoms.dm"
@@ -544,6 +543,7 @@
#include "code\__HELPERS\varset_callback.dm"
#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\view.dm"
+#include "code\__HELPERS\visual_effects.dm"
#include "code\__HELPERS\weakref.dm"
#include "code\__HELPERS\logging\_logging.dm"
#include "code\__HELPERS\logging\admin.dm"
@@ -1899,10 +1899,6 @@
#include "code\datums\wounds\slash.dm"
#include "code\datums\wounds\scars\_scars.dm"
#include "code\datums\wounds\scars\_static_scar_data.dm"
-#include "code\game\alternate_appearance.dm"
-#include "code\game\atom_defense.dm"
-#include "code\game\atoms.dm"
-#include "code\game\atoms_initializing_EXPENSIVE.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\communications.dm"
#include "code\game\data_huds.dm"
@@ -1938,6 +1934,22 @@
#include "code\game\area\areas\station\service.dm"
#include "code\game\area\areas\station\solars.dm"
#include "code\game\area\areas\station\telecomm.dm"
+#include "code\game\atom\_atom.dm"
+#include "code\game\atom\alternate_appearance.dm"
+#include "code\game\atom\atom_act.dm"
+#include "code\game\atom\atom_appearance.dm"
+#include "code\game\atom\atom_color.dm"
+#include "code\game\atom\atom_defense.dm"
+#include "code\game\atom\atom_examine.dm"
+#include "code\game\atom\atom_greyscale.dm"
+#include "code\game\atom\atom_invisibility.dm"
+#include "code\game\atom\atom_materials.dm"
+#include "code\game\atom\atom_merger.dm"
+#include "code\game\atom\atom_orbit.dm"
+#include "code\game\atom\atom_storage.dm"
+#include "code\game\atom\atom_tool_acts.dm"
+#include "code\game\atom\atom_vv.dm"
+#include "code\game\atom\atoms_initializing_EXPENSIVE.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\game_mode.dm"
#include "code\game\gamemodes\objective.dm"