Files
Aurora.3/code/game/atoms_movable.dm
VMSolidus 8998842f7a Large Variety Of Hard Dels (#21834)
I spent the entirety of today's event looking at hard dels with my new
digital minions. This was *nearly* every Hard Del that came up during
2/7/2026's event. It turns out that AI is extremely well suited to
hunting down circular references like this across an entire repo. This
PR was made with Antigravity-Gemini3Pro.

I have not yet tested this PR.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Wildkins <john.wildkins@gmail.com>
2026-02-17 15:57:11 +00:00

1080 lines
38 KiB
Plaintext

/atom/movable
layer = OBJ_LAYER
glide_size = 8
animate_movement = SLIDE_STEPS
appearance_flags = DEFAULT_APPEARANCE_FLAGS | TILE_BOUND | LONG_GLIDE
var/last_move = null
/// A list containing arguments for Moved().
VAR_PRIVATE/tmp/list/active_movement
var/anchored = FALSE
var/movable_flags
///Used to scale icons up or down horizonally in update_transform().
var/icon_scale_x = 1
///Used to scale icons up or down vertically in update_transform().
var/icon_scale_y = 1
///Used to rotate icons in update_transform()
var/icon_rotation = 0
var/move_speed = 10
var/l_move_time = 1
var/datum/thrownthing/throwing = null
var/throw_speed = 2
var/throw_range = 7
var/moved_recently = 0
var/atom/movable/pulledby = null
///Base name of the image used for when the item is in someone's hand. Suffixes are added to this. Doubles as legacy overlay_state.
var/item_state = null
///Base name of the image used for when the item is worn. Suffixes are added to this. Important for icon flipping as _flip is added at the end of the value.
var/overlay_state = null
///Also used on holdable mobs for the same info related to their held version
var/does_spin = TRUE // Does the atom spin when thrown (of course it does :P)
var/can_hold_mob = FALSE
var/list/contained_mobs
///Are we moving with inertia? Mostly used as an optimization
var/inertia_moving = FALSE
///Delay in deciseconds between inertia based movement
var/inertia_move_delay = 5
///0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/moving_diagonally = 0
///Holds information about any movement loops currently running/waiting to run on the movable. Lazy, will be null if nothing's going on
var/datum/movement_packet/move_packet
/**
* an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type))
* each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration
* do NOT add channels to this for little reason as it can add considerable memory usage.
*/
var/list/important_recursive_contents
/// String representing the spatial grid groups we want to be held in.
/// acts as a key to the list of spatial grid contents types we exist in via SSspatial_grid.spatial_grid_categories.
/// We do it like this to prevent people trying to mutate them and to save memory on holding the lists ourselves
var/spatial_grid_key
/**
* In case you have multiple types, you automatically use the most useful one.
* IE: Skating on ice, flippers on water, flying over chasm/space, etc.
* I recommend you use the movetype_handler system and not modify this directly, especially for living mobs. -- this isn't in our codebase yet, however
*/
var/movement_type = GROUND
var/datum/component/orbiter/orbiting
/// Either [EMISSIVE_BLOCK_NONE], [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
var/blocks_emissive = EMISSIVE_BLOCK_NONE
///Internal holder for emissive blocker object, DO NOT USE DIRECTLY. Use blocks_emissive
var/mutable_appearance/em_block
///Lazylist to keep track on the sources of illumination.
var/list/affected_movable_lights
///Highest-intensity light affecting us, which determines our visibility.
var/affecting_dynamic_lumi = 0
/// Holds a reference to the emissive blocker overlay
var/emissive_overlay
/// Whether this atom should have its dir automatically changed when it moves. Setting this to FALSE allows for things such as directional windows to retain dir on moving without snowflake code all of the place.
var/set_dir_on_move = TRUE
/atom/movable/Initialize(mapload, ...)
. = ..()
update_emissive_blocker()
if(opacity)
AddElement(/datum/element/light_blocking)
if(light_system == MOVABLE_LIGHT)
AddComponent(/datum/component/overlay_lighting)
if(light_system == DIRECTIONAL_LIGHT)
AddComponent(/datum/component/overlay_lighting, is_directional = TRUE)
/atom/movable/Destroy(force)
if(orbiting)
orbiting.end_orbit(src)
QDEL_NULL(emissive_overlay)
if(move_packet)
if(!QDELETED(move_packet))
qdel(move_packet)
move_packet = null
if(spatial_grid_key)
SSspatial_grid.force_remove_from_grid(src)
QDEL_LAZYLIST(contained_mobs)
. = ..()
for(var/movable_content in contents)
qdel(movable_content)
//Pretend this is moveToNullspace()
moveToNullspace()
loc = null
//This absolutely must be after moveToNullspace()
//We rely on Entered and Exited to manage this list, and the copy of this list that is on any /atom/movable "Containers"
//If we clear this before the nullspace move, a ref to this object will be hung in any of its movable containers
LAZYNULL(important_recursive_contents)
vis_locs = null //clears this atom out of all viscontents
// Checking length(vis_contents) before cutting has significant speed benefits
if (length(vis_contents))
vis_contents.Cut()
screen_loc = null
if(ismob(pulledby))
var/mob/M = pulledby
if(M.pulling == src)
M.pulling = null
pulledby = null
if (bound_overlay)
QDEL_NULL(bound_overlay)
if(em_block)
QDEL_NULL(em_block)
QDEL_NULL(light)
QDEL_NULL(static_light)
/atom/movable/proc/moveToNullspace()
. = TRUE
var/atom/oldloc = loc
if (oldloc)
loc = null
var/area/old_area = get_area(oldloc)
if(isturf(oldloc)) //This checked if it's a multitile, which i have no clue what would be here, so just check for turf
for(var/atom/old_loc as anything in locs)
old_loc.Exited(src, NONE)
else
oldloc.Exited(src, NONE)
if(old_area)
old_area.Exited(src, NONE)
RESOLVE_ACTIVE_MOVEMENT
// Make sure you know what you're doing if you call this
// You probably want CanPass()
/atom/movable/Cross(atom/movable/crossed_atom)
. = TRUE
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, crossed_atom)
SEND_SIGNAL(crossed_atom, COMSIG_MOVABLE_CROSS_OVER, src)
// return CanPass(crossed_atom, get_dir(src, crossed_atom))
return CanPass(crossed_atom, get_step(src, get_dir(src, crossed_atom)), 1, 0)
///default byond proc that is deprecated for us in lieu of signals. do not call
/atom/movable/Crossed(atom/movable/crossed_atom, oldloc)
SHOULD_NOT_OVERRIDE(TRUE)
CRASH("atom/movable/Crossed() was called!")
/**
* `Uncross()` is a default BYOND proc that is called when something is *going*
* to exit this atom's turf. It is prefered over `Uncrossed` when you want to
* deny that movement, such as in the case of border objects, objects that allow
* you to walk through them in any direction except the one they block
* (think side windows).
*
* While being seemingly harmless, most everything doesn't actually want to
* use this, meaning that we are wasting proc calls for every single atom
* on a turf, every single time something exits it, when basically nothing
* cares.
*
* This overhead caused real problems on Sybil round #159709, where lag
* attributed to Uncross was so bad that the entire master controller
* collapsed and people made Among Us lobbies in OOC.
*
* If you want to replicate the old `Uncross()` behavior, the most apt
* replacement is [`/datum/element/connect_loc`] while hooking onto
* [`COMSIG_ATOM_EXIT`].
*/
/atom/movable/Uncross()
SHOULD_NOT_OVERRIDE(TRUE)
. = TRUE
CRASH("Uncross() should not be being called, please read the doc-comment for it for why.")
/**
* default byond proc that is normally called on everything inside the previous turf
* a movable was in after moving to its current turf
* this is wasteful since the vast majority of objects do not use Uncrossed
* use connect_loc to register to COMSIG_ATOM_EXITED instead
*/
/atom/movable/Uncrossed(atom/movable/uncrossed_atom)
SHOULD_NOT_OVERRIDE(TRUE)
CRASH("/atom/movable/Uncrossed() was called")
/**
* Pretend this is `Bump()`
*/
/atom/movable/proc/Collide(atom/bumped_atom)
SHOULD_NOT_SLEEP(TRUE)
if(!bumped_atom)
CRASH("Bump was called with no argument.")
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
// . = ..() when we turn it into Bump()
if(!QDELETED(throwing))
throwing.finalize(hit = TRUE, target = bumped_atom)
. = TRUE
if(QDELETED(bumped_atom))
return
bumped_atom.CollidedWith(src)
//Aurora snowflake atom airflow hit
if(airflow_speed > 0 && airflow_dest)
airflow_hit(bumped_atom)
else
airflow_speed = 0
airflow_time = 0
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
var/hitpush = TRUE
var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum)
if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH)
hitpush = FALSE // hacky, tie this to something else or a proper workaround later
if(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))
return // in case a signal interceptor broke or deleted the thing before we could process our hit
if(SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum) & COMSIG_HIT_PREVENTED)
return
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
//decided whether a movable atom being thrown can pass through the turf it is in.
/atom/movable/proc/hit_check(var/speed, var/target)
if(throwing)
for(var/atom/A in get_turf(src))
if(A == src)
continue
if(isliving(A))
var/mob/living/M = A
if(M.lying && M != target)
continue
throw_impact(A, speed)
if(isobj(A))
if(A.density && !A.CanPass(src, target))
src.throw_impact(A,speed)
// Prevents robots dropping their modules
/atom/movable/proc/dropsafety()
if(!istype(src.loc))
return TRUE
if (issilicon(src.loc))
return FALSE
if (istype(src.loc, /obj/item/rig_module))
return FALSE
return TRUE
/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE)
. = FALSE
if(QDELETED(src))
CRASH("Qdeleted thing being thrown around.")
if (!target || speed <= 0)
return
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW)
return
// if (pulledby)
// pulledby.stop_pulling()
//They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
var/user_momentum = thrower.cached_multiplicative_slowdown
if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
user_momentum = world.tick_lag
user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
if (get_dir(thrower, target) & last_move)
user_momentum = user_momentum //basically a noop, but needed
else if (get_dir(target, thrower) & last_move)
user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
else
user_momentum = 0
if (user_momentum)
//first lets add that momentum to range.
range *= (user_momentum / speed) + 1
//then lets add it to speed
speed += user_momentum
if (speed <= 0)
return//no throw speed, the user was moving too fast.
. = TRUE // No failure conditions past this point.
var/target_zone
if(QDELETED(thrower))
thrower = null //Let's not pass a qdeleting reference if any.
else
target_zone = thrower.zone_sel
var/datum/thrownthing/thrown_thing = new(src, target, get_dir(src, target), range, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
var/dx = (target.x > src.x) ? EAST : WEST
var/dy = (target.y > src.y) ? NORTH : SOUTH
if (dist_x == dist_y)
thrown_thing.pure_diagonal = 1
else if(dist_x <= dist_y)
var/olddist_x = dist_x
var/olddx = dx
dist_x = dist_y
dist_y = olddist_x
dx = dy
dy = olddx
thrown_thing.dist_x = dist_x
thrown_thing.dist_y = dist_y
thrown_thing.dx = dx
thrown_thing.dy = dy
thrown_thing.diagonal_error = dist_x/2 - dist_y
thrown_thing.start_time = world.time
// if(pulledby)
// pulledby.stop_pulling()
if (quickstart && (throwing || SSthrowing.state == SS_RUNNING)) //Avoid stack overflow edgecases.
quickstart = FALSE
throwing = thrown_thing
if(spin)
SpinAnimation(5, 1)
SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, thrown_thing, spin)
SSthrowing.processing[src] = thrown_thing
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
SSthrowing.currentrun[src] = thrown_thing
if (quickstart)
thrown_thing.tick()
/atom/movable/proc/throw_at_random(var/include_own_turf, var/maxrange, var/speed)
var/list/turfs = RANGE_TURFS(maxrange, src)
if(!maxrange)
maxrange = 1
if(!include_own_turf)
turfs -= get_turf(src)
src.throw_at(pick(turfs), maxrange, speed)
/atom/movable/proc/update_emissive_blocker()
if(emissive_overlay)
CutOverlays(emissive_overlay)
switch(blocks_emissive)
if(EMISSIVE_BLOCK_GENERIC)
var/mutable_appearance/gen_emissive_blocker = mutable_appearance(icon, icon_state, plane = EMISSIVE_PLANE, alpha = src.alpha)
gen_emissive_blocker.color = GLOB.em_block_color
gen_emissive_blocker.dir = dir
gen_emissive_blocker.appearance_flags |= appearance_flags
emissive_overlay = gen_emissive_blocker
AddOverlays(emissive_overlay)
if(EMISSIVE_BLOCK_UNIQUE)
render_target = REF(src)
em_block = new(src, render_target)
emissive_overlay = em_block
AddOverlays(emissive_overlay)
/atom/movable/update_icon()
..()
UPDATE_OO_IF_PRESENT
if (em_block)
CutOverlays(em_block)
update_emissive_blocker()
if (em_block)
AddOverlays(em_block)
//Overlays
/atom/movable/overlay
var/atom/master = null
anchored = 1
/atom/movable/overlay/New()
verbs.Cut()
..()
/atom/movable/overlay/Destroy(force)
master = null
. = ..()
/atom/movable/overlay/attackby(obj/item/attacking_item, mob/user, params)
if (src.master)
return src.master.attackby(arglist(args))
return
/atom/movable/overlay/attack_hand(a, b, c)
if (src.master)
return src.master.attack_hand(a, b, c)
return
/atom/movable/proc/touch_map_edge()
if(z in SSatlas.current_map.sealed_levels)
return
if(anchored)
return
if(!GLOB.universe.OnTouchMapEdge(src))
return
if(SSatlas.current_map.use_overmap)
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(overmap_spacetravel), get_turf(src), src)
return
var/move_to_z = src.get_transit_zlevel()
if(move_to_z)
z = move_to_z
if(x <= TRANSITIONEDGE)
x = world.maxx - TRANSITIONEDGE - 2
y = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2)
else if (x >= (world.maxx - TRANSITIONEDGE + 1))
x = TRANSITIONEDGE + 1
y = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2)
else if (y <= TRANSITIONEDGE)
y = world.maxy - TRANSITIONEDGE -2
x = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2)
else if (y >= (world.maxy - TRANSITIONEDGE + 1))
y = TRANSITIONEDGE + 1
x = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2)
if(istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
var/datum/game_mode/nuclear/G = SSticker.mode
G.check_nuke_disks()
if(loc)
var/turf/T = loc
loc.Entered(src)
if(!T.is_hole)
fall_impact(text2num(pickweight(list("1" = 60, "2" = 30, "3" = 10))))
//by default, transition randomly to another zlevel
/atom/movable/proc/get_transit_zlevel()
return SSatlas.current_map.get_transit_zlevel()
// Returns the current scaling of the sprite.
// Note this DOES NOT measure the height or width of the icon, but returns what number is being multiplied with to scale the icons, if any.
/atom/movable/proc/get_icon_scale_x()
return icon_scale_x
/atom/movable/proc/get_icon_scale_y()
return icon_scale_y
/atom/movable/proc/update_transform()
var/matrix/M = matrix()
M.Scale(icon_scale_x, icon_scale_y)
M.Turn(icon_rotation)
src.transform = M
// Use this to set the object's scale.
/atom/movable/proc/adjust_scale(new_scale_x, new_scale_y)
if(isnull(new_scale_y))
new_scale_y = new_scale_x
if(new_scale_x != 0)
icon_scale_x = new_scale_x
if(new_scale_y != 0)
icon_scale_y = new_scale_y
update_transform()
/atom/movable/proc/adjust_rotation(new_rotation)
icon_rotation = new_rotation
update_transform()
// Core movement hooks & procs.
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
RESOLVE_ACTIVE_MOVEMENT
if(!destination)
return FALSE
if(loc)
loc.Exited(src, destination)
var/oldloc = loc
SET_ACTIVE_MOVEMENT(oldloc, NONE, TRUE, null)
loc = destination
loc.Entered(src, oldloc)
Moved(oldloc, get_dir(oldloc, destination), TRUE)
//Zmimic
if(bound_overlay)
// The overlay will handle cleaning itself up on non-openspace turfs.
if (isturf(destination))
bound_overlay.forceMove(get_step(src, UP))
if (dir != bound_overlay.dir)
bound_overlay.set_dir(dir)
else // Not a turf, so we need to destroy immediately instead of waiting for the destruction timer to proc.
qdel(bound_overlay)
if(opacity)
updateVisibility(src)
RESOLVE_ACTIVE_MOVEMENT
return TRUE
/**
* Called after a successful Move(). By this point, we've already moved.
* Arguments:
* * old_loc is the location prior to the move. Can be null to indicate nullspace.
* * movement_dir is the direction the movement took place. Can be NONE if it was some sort of teleport.
* * The forced flag indicates whether this was a forced move, which skips many checks of regular movement.
* * The old_locs is an optional argument, in case the moved movable was present in multiple locations before the movement.
**/
/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced, list/old_locs)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, forced)
/* START Spatial grid stuffs */
if(!HAS_SPATIAL_GRID_CONTENTS(src) || !SSspatial_grid.initialized)
return
var/turf/old_turf = get_turf(old_loc)
var/turf/new_turf = get_turf(src)
if(old_turf && new_turf && (old_turf.z != new_turf.z \
|| ROUND_UP(old_turf.x / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.x / SPATIAL_GRID_CELLSIZE) \
|| ROUND_UP(old_turf.y / SPATIAL_GRID_CELLSIZE) != ROUND_UP(new_turf.y / SPATIAL_GRID_CELLSIZE)))
SSspatial_grid.exit_cell(src, old_turf)
SSspatial_grid.enter_cell(src, new_turf)
else if(old_turf && !new_turf)
SSspatial_grid.exit_cell(src, old_turf)
else if(new_turf && !old_turf)
SSspatial_grid.enter_cell(src, new_turf)
/* END Spatial grid stuffs */
for(var/datum/dynamic_light_source/light as anything in hybrid_light_sources)
light.source_atom.update_light()
if(!isturf(loc))
light.find_containing_atom()
for(var/datum/static_light_source/L as anything in static_light_sources) // Cycle through the light sources on this atom and tell them to update.
L.source_atom.static_update_light()
/atom/movable/Exited(atom/movable/gone, direction)
. = ..()
if(LAZYLEN(gone.important_recursive_contents))
var/list/nested_locs = get_nested_locs(src) + src
for(var/channel in gone.important_recursive_contents)
for(var/atom/movable/location as anything in nested_locs)
LAZYINITLIST(location.important_recursive_contents)
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
LAZYINITLIST(recursive_contents[channel])
recursive_contents[channel] -= gone.important_recursive_contents[channel]
switch(channel)
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE,RECURSIVE_CONTENTS_AI_TARGETS)
if(!length(recursive_contents[channel]))
// This relies on a nice property of the linked recursive and gridmap types
// They're defined in relation to each other, so they have the same value
SSspatial_grid.remove_grid_awareness(location, channel)
ASSOC_UNSETEMPTY(recursive_contents, channel)
UNSETEMPTY(location.important_recursive_contents)
GLOB.dir_set_event.unregister(src, gone, TYPE_PROC_REF(/atom, recursive_dir_set))
/atom/movable/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..()
if(LAZYLEN(arrived.important_recursive_contents))
var/list/nested_locs = get_nested_locs(src) + src
for(var/channel in arrived.important_recursive_contents)
for(var/atom/movable/location as anything in nested_locs)
LAZYINITLIST(location.important_recursive_contents)
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
LAZYINITLIST(recursive_contents[channel])
switch(channel)
if(RECURSIVE_CONTENTS_CLIENT_MOBS, RECURSIVE_CONTENTS_HEARING_SENSITIVE,RECURSIVE_CONTENTS_AI_TARGETS)
if(!length(recursive_contents[channel]))
SSspatial_grid.add_grid_awareness(location, channel)
recursive_contents[channel] |= arrived.important_recursive_contents[channel]
if(GLOB.dir_set_event.has_listeners(arrived))
GLOB.dir_set_event.register(src, arrived, TYPE_PROC_REF(/atom, recursive_dir_set))
///allows this movable to hear and adds itself to the important_recursive_contents list of itself and every movable loc its in
/atom/movable/proc/become_hearing_sensitive(trait_source = TRAIT_GENERIC)
ADD_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
return
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYINITLIST(location.important_recursive_contents)
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
SSspatial_grid.add_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] += list(src)
var/turf/our_turf = get_turf(src)
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
/**
* removes the hearing sensitivity channel from the important_recursive_contents list of this and all nested locs containing us if there are no more sources of the trait left
* since RECURSIVE_CONTENTS_HEARING_SENSITIVE is also a spatial grid content type, removes us from the spatial grid if the trait is removed
*
* * trait_source - trait source define or ALL, if ALL, force removes hearing sensitivity. if a trait source define, removes hearing sensitivity only if the trait is removed
*/
/atom/movable/proc/lose_hearing_sensitivity(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
return
REMOVE_TRAIT(src, TRAIT_HEARING_SENSITIVE, trait_source)
if(HAS_TRAIT(src, TRAIT_HEARING_SENSITIVE))
return
var/turf/our_turf = get_turf(src)
/// We get our awareness updated by the important recursive contents stuff, here we remove our membership
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
var/list/recursive_contents = location.important_recursive_contents // blue hedgehog velocity
recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE] -= src
if(!length(recursive_contents[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
SSspatial_grid.remove_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_HEARING)
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_HEARING_SENSITIVE)
UNSETEMPTY(location.important_recursive_contents)
///allows this movable to know when it has "entered" another area no matter how many movable atoms its stuffed into, uses important_recursive_contents
/atom/movable/proc/become_area_sensitive(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
ADD_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
///removes the area sensitive channel from the important_recursive_contents list of this and all nested locs containing us if there are no more source of the trait left
/atom/movable/proc/lose_area_sensitivity(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
return
REMOVE_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
if(HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
return
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
///propogates ourselves through our nested contents, similar to other important_recursive_contents procs
///main difference is that client contents need to possibly duplicate recursive contents for the clients mob AND its eye
/mob/proc/enable_client_mobs_in_contents()
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
LAZYINITLIST(movable_loc.important_recursive_contents)
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
SSspatial_grid.add_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] |= src
var/turf/our_turf = get_turf(src)
/// We got our awareness updated by the important recursive contents stuff, now we add our membership
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
///Clears the clients channel of this mob
/mob/proc/clear_important_client_contents()
var/turf/our_turf = get_turf(src)
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
LAZYINITLIST(movable_loc.important_recursive_contents)
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS])
recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS] -= src
if(!length(recursive_contents[RECURSIVE_CONTENTS_CLIENT_MOBS]))
SSspatial_grid.remove_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS)
UNSETEMPTY(movable_loc.important_recursive_contents)
// This proc adds atom/movables to the AI targetable list, i.e. things that the AI (turrets, hostile animals) will attempt to target
/atom/movable/proc/add_to_target_grid()
for (var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYINITLIST(location.important_recursive_contents)
var/list/recursive_contents = location.important_recursive_contents
if(!length(recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS]))
SSspatial_grid.add_grid_awareness(location, SPATIAL_GRID_CONTENTS_TYPE_TARGETS)
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS])
recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS] |= src
var/turf/our_turf = get_turf(src)
SSspatial_grid.add_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_TARGETS)
/atom/movable/proc/clear_from_target_grid()
var/turf/our_turf = get_turf(src)
SSspatial_grid.remove_grid_membership(src, our_turf, SPATIAL_GRID_CONTENTS_TYPE_TARGETS)
for(var/atom/movable/movable_loc as anything in get_nested_locs(src) + src)
LAZYINITLIST(movable_loc.important_recursive_contents)
var/list/recursive_contents = movable_loc.important_recursive_contents // blue hedgehog velocity
LAZYINITLIST(recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS])
recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS] -= src
if(!length(recursive_contents[RECURSIVE_CONTENTS_AI_TARGETS]))
SSspatial_grid.remove_grid_awareness(movable_loc, SPATIAL_GRID_CONTENTS_TYPE_TARGETS)
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_AI_TARGETS)
UNSETEMPTY(movable_loc.important_recursive_contents)
/atom/movable/proc/do_simple_ranged_interaction(var/mob/user)
return FALSE
/atom/movable/proc/get_bullet_impact_effect_type()
return BULLET_IMPACT_NONE
// /atom/movable/proc/do_pickup_animation(atom/target, var/image/pickup_animation = image(icon, loc, icon_state, ABOVE_ALL_MOB_LAYER, dir, pixel_x, pixel_y))
/obj/item/proc/do_pickup_animation(atom/target, turf/source)
if(!source)
if(!istype(loc, /turf))
return
source = loc
var/image/pickup_animation = image(icon = src)
pickup_animation.transform.Scale(0.75)
pickup_animation.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
var/direction = get_dir(source, target)
var/to_x = target.pixel_x
var/to_y = target.pixel_y
if(direction & NORTH)
to_y += 32
else if(direction & SOUTH)
to_y -= 32
if(direction & EAST)
to_x += 32
else if(direction & WEST)
to_x -= 32
if(!direction)
to_y += 10
pickup_animation.pixel_x += 6 * (prob(50) ? 1 : -1) //6 to the right or left, helps break up the straight upward move
var/atom/movable/flick_visual/pickup = source.flick_overlay_view(pickup_animation, 0.4 SECONDS)
var/matrix/animation_matrix = new(pickup.transform)
animation_matrix.Turn(pick(-30, 30))
animation_matrix.Scale(0.65)
animate(pickup, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 0.3 SECONDS, transform = animation_matrix, easing = CUBIC_EASING)
animate(alpha = 0, transform = matrix().Scale(0.7), time = 0.1 SECONDS)
/atom/movable/proc/do_drop_animation(atom/moving_from)
if(!isturf(loc))
return
var/turf/current_turf = get_turf(src)
var/direction = get_dir(moving_from, current_turf)
var/from_x = 0
var/from_y = 0
if(direction & NORTH)
from_y -= 32
else if(direction & SOUTH)
from_y += 32
if(direction & EAST)
from_x -= 32
else if(direction & WEST)
from_x += 32
if(!direction)
from_y += 10
from_x += 6 * (prob(50) ? 1 : -1) //6 to the right or left, helps break up the straight upward move
//We're moving from these chords to our current ones
var/old_x = pixel_x
var/old_y = pixel_y
var/old_alpha = alpha
var/matrix/old_transform = transform
var/matrix/animation_matrix = new(old_transform)
animation_matrix.Turn(pick(-30, 30))
animation_matrix.Scale(0.7) // Shrink to start, end up normal sized
pixel_x = from_x
pixel_y = from_y
alpha = 0
transform = animation_matrix
// This is instant on byond's end, but to our clients this looks like a quick drop
animate(src, alpha = old_alpha, pixel_x = old_x, pixel_y = old_y, transform = old_transform, time = 3, easing = CUBIC_EASING)
/atom/movable/proc/get_floating_chat_x_offset()
return 0
/atom/movable/proc/get_floating_chat_y_offset()
return 8
/atom/movable/proc/can_attach_sticker(var/mob/user, var/obj/item/sticker/S)
return TRUE
/atom/movable/proc/too_heavy_to_throw()
return FALSE
/atom/movable/proc/begin_falling(var/lastloc, var/below)
return
/atom/movable/proc/show_message(msg, type, alt, alt_type) //Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
return
/**
* Called whenever an object moves and by mobs when they attempt to move themselves through space
* And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
*
* Return FALSE to have src start/keep drifting in a no-grav area and TRUE to stop/not start drifting
*
* Mobs should return 1 if they should be able to move of their own volition, see [/client/proc/Move]
*
* Arguments:
* * movement_dir - 0 when stopping or any dir when trying to move
* * continuous_move - If this check is coming from something in the context of already drifting
*/
/atom/movable/proc/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
if(has_gravity())
return TRUE
if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE)
return TRUE
if(pulledby && (pulledby.pulledby != src)) //Different from TG
return TRUE
if(throwing)
return TRUE
if(!isturf(loc))
return TRUE
if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
return TRUE
return FALSE
/// Only moves the object if it's under no gravity
/// Accepts the direction to move, if the push should be instant, and an optional parameter to fine tune the start delay
/atom/movable/proc/newtonian_move(direction, instant = FALSE, start_delay = 0)
if(!isturf(loc) || Process_Spacemove(direction, continuous_move = TRUE))
return FALSE
if(SEND_SIGNAL(src, COMSIG_MOVABLE_NEWTONIAN_MOVE, direction, start_delay) & COMPONENT_MOVABLE_NEWTONIAN_BLOCK)
return TRUE
AddComponent(/datum/component/drift, direction, instant, start_delay)
return TRUE
/**
* meant for movement with zero side effects. only use for objects that are supposed to move "invisibly" (like camera mobs or ghosts)
* if you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this
* most of the time you want forceMove()
*/
/atom/movable/proc/abstract_move(atom/new_loc)
RESOLVE_ACTIVE_MOVEMENT // This should NEVER happen, but, just in case...
var/atom/old_loc = loc
var/direction = get_dir(old_loc, new_loc)
loc = new_loc
//is Moved(old_loc, direction, TRUE, momentum_change = FALSE) in tg
Moved(old_loc, direction, TRUE)
////////////////////////////////////////
// Here's where we rewrite how byond handles movement except slightly different
// To be removed on step_ conversion
// All this work to prevent a second bump
/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, update_dir = TRUE) //Last 2 parameters are not used but they're caught
CAN_BE_REDEFINED(TRUE)
. = FALSE
if(!newloc || newloc == loc)
return
// A mid-movement... movement... occurred, resolve that first.
RESOLVE_ACTIVE_MOVEMENT
if(!direction)
direction = get_dir(src, newloc)
// TEMPORARY OFF because i remember this giving some issues on something i don't quite remember
if(set_dir_on_move && dir != direction && update_dir)
set_dir(direction)
//There should be some multitile code above, it was cut as we don't do multitile movement (yet)
if(!loc.Exit(src, direction))
return
//There should be some multitile code above, it was cut as we don't do multitile movement (yet)
if(!newloc.Enter(src))
return
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
return
var/atom/oldloc = loc
var/area/oldarea = get_area(oldloc)
var/area/newarea = get_area(newloc)
SET_ACTIVE_MOVEMENT(oldloc, direction, FALSE, list()) //This is different from TG because we don't have multitile movement (yet)
loc = newloc
. = TRUE
oldloc.Exited(src, direction)
if(oldarea != newarea)
oldarea.Exited(src, direction)
newloc.Entered(src, oldloc) //This is different from TG because we don't have multitile movement (yet)
if(oldarea != newarea)
newarea.Entered(src, oldarea)
// Openturf.
if(bound_overlay)
// The overlay will handle cleaning itself up on non-openspace turfs.
bound_overlay.forceMove(get_step(src, UP))
if(bound_overlay.dir != dir)
bound_overlay.set_dir(dir)
if(opacity)
updateVisibility(src)
//Mimics
if(bound_overlay)
bound_overlay.forceMove(get_step(src, UP))
if(bound_overlay.dir != dir)
bound_overlay.set_dir(dir)
src.move_speed = world.time - src.l_move_time
src.l_move_time = world.time
if ((oldloc != src.loc && oldloc && oldloc.z == src.z))
src.last_move = get_dir(oldloc, src.loc)
RESOLVE_ACTIVE_MOVEMENT
////////////////////////////////////////
/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
CAN_BE_REDEFINED(TRUE)
// var/atom/movable/pullee = pulling
// var/turf/current_turf = loc
if(!loc || !newloc)
return FALSE
if(loc != newloc)
if (!(direct & (direct - 1))) //Cardinal move
. = ..()
else //Diagonal move, split it into cardinal moves
moving_diagonally = FIRST_DIAG_STEP
var/first_step_dir
// The `&& moving_diagonally` checks are so that a forceMove taking
// place due to a Crossed, Bumped, etc. call will interrupt
// the second half of the diagonal movement, or the second attempt
// at a first half if step() fails because we hit something.
if (direct & NORTH)
if (direct & EAST)
if (step(src, NORTH) && moving_diagonally)
first_step_dir = NORTH
moving_diagonally = SECOND_DIAG_STEP
. = step(src, EAST)
else if (moving_diagonally && step(src, EAST))
first_step_dir = EAST
moving_diagonally = SECOND_DIAG_STEP
. = step(src, NORTH)
else if (direct & WEST)
if (step(src, NORTH) && moving_diagonally)
first_step_dir = NORTH
moving_diagonally = SECOND_DIAG_STEP
. = step(src, WEST)
else if (moving_diagonally && step(src, WEST))
first_step_dir = WEST
moving_diagonally = SECOND_DIAG_STEP
. = step(src, NORTH)
else if (direct & SOUTH)
if (direct & EAST)
if (step(src, SOUTH) && moving_diagonally)
first_step_dir = SOUTH
moving_diagonally = SECOND_DIAG_STEP
. = step(src, EAST)
else if (moving_diagonally && step(src, EAST))
first_step_dir = EAST
moving_diagonally = SECOND_DIAG_STEP
. = step(src, SOUTH)
else if (direct & WEST)
if (step(src, SOUTH) && moving_diagonally)
first_step_dir = SOUTH
moving_diagonally = SECOND_DIAG_STEP
. = step(src, WEST)
else if (moving_diagonally && step(src, WEST))
first_step_dir = WEST
moving_diagonally = SECOND_DIAG_STEP
. = step(src, SOUTH)
if(moving_diagonally == SECOND_DIAG_STEP)
if(!. && set_dir_on_move && update_dir)
set_dir(first_step_dir)
else if(!inertia_moving)
newtonian_move(dir2angle(direct))
moving_diagonally = 0
return
last_move = direct
if(set_dir_on_move && dir != direct && update_dir)
set_dir(direct)
/**
* Adds the red drop-shadow filter when pointed to by a mob.
* Override if the atom doesn't play nice with filters.
*/
/atom/movable/proc/add_point_filter()
add_filter("pointglow", 1, list(type = "drop_shadow", x = 0, y = -1, offset = 1, size = 1, color = "#F00"))
addtimer(CALLBACK(src, PROC_REF(remove_filter), "pointglow"), 2 SECONDS)
///Keeps track of the sources of dynamic luminosity and updates our visibility with the highest.
/atom/movable/proc/update_dynamic_luminosity()
var/highest = 0
for(var/i in affected_movable_lights)
if(affected_movable_lights[i] <= highest)
continue
highest = affected_movable_lights[i]
if(highest == affecting_dynamic_lumi)
return
luminosity -= affecting_dynamic_lumi
affecting_dynamic_lumi = highest
luminosity += affecting_dynamic_lumi
///Helper to change several lighting overlay settings.
/atom/movable/proc/set_light_range_power_color(range, power, color)
set_light_range(range)
set_light_power(power)
set_light_color(color)
/**
* Called when a movable is moved by a shuttle. Eventually, God willing, replace this with the TG shuttle version.
*/
/atom/movable/proc/afterShuttleMove(obj/effect/shuttle_landmark/destination)
if(light)
update_light()
/atom/movable/proc/set_glide_size(target = 8)
if (HAS_TRAIT(src, TRAIT_NO_GLIDE))
return
SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
glide_size = target