Files
Bubberstation/code/game/atoms_movable.dm
T
70024ef6ad [MIRROR] Reverts Adds Neon Carpet (#55782) (#3040)
* reverts Adds Neon Carpet (#55782), it appears to have been causing massive amounts of maptick (#56496)

It appears to have been causing massive amounts of maptick, this'll need a testmerge so we can test my hypothesis

* Reverts Adds Neon Carpet (#55782)

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
2021-02-03 10:22:43 +01:00

1116 lines
37 KiB
Plaintext

/atom/movable
layer = OBJ_LAYER
glide_size = 8
appearance_flags = TILE_BOUND|PIXEL_SCALE
var/last_move = null
var/last_move_time = 0
var/anchored = FALSE
var/move_resist = MOVE_RESIST_DEFAULT
var/move_force = MOVE_FORCE_DEFAULT
var/pull_force = PULL_FORCE_DEFAULT
var/datum/thrownthing/throwing = null
var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported
var/throw_range = 7
///Max range this atom can be thrown via telekinesis
var/tk_throw_range = 1
var/mob/pulledby = null
var/initial_language_holder = /datum/language_holder
var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence.
var/verb_say = "says"
var/verb_ask = "asks"
var/verb_exclaim = "exclaims"
var/verb_whisper = "whispers"
var/verb_sing = "sings"
var/verb_yell = "yells"
var/speech_span
var/inertia_dir = 0
var/atom/inertia_last_loc
var/inertia_moving = 0
var/inertia_next_move = 0
var/inertia_move_delay = 5
/// Things we can pass through while moving. If any of this matches the thing we're trying to pass's [pass_flags_self], then we can pass through.
var/pass_flags = NONE
/// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour
var/generic_canpass = TRUE
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
/**
* 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 reccomend you use the movetype_handler system and not modify this directly, especially for living mobs.
*/
var/movement_type = GROUND
var/atom/movable/pulling
var/grab_state = 0
var/throwforce = 0
var/datum/component/orbiter/orbiting
var/can_be_z_moved = TRUE
var/zfalling = FALSE
///Last location of the atom for demo recording purposes
var/atom/demo_last_loc
/// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
var/blocks_emissive = FALSE
///Internal holder for emissive blocker object, do not use directly use blocks_emissive
var/atom/movable/emissive_blocker/em_block
///Used for the calculate_adjacencies proc for icon smoothing.
var/can_be_unanchored = FALSE
///Lazylist to keep track on the sources of illumination.
var/list/affected_dynamic_lights
///Highest-intensity light affecting us, which determines our visibility.
var/affecting_dynamic_lumi = 0
/// 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)
. = ..()
switch(blocks_emissive)
if(EMISSIVE_BLOCK_GENERIC)
update_emissive_block()
if(EMISSIVE_BLOCK_UNIQUE)
render_target = ref(src)
em_block = new(src, render_target)
vis_contents += em_block
if(opacity)
AddElement(/datum/element/light_blocking)
switch(light_system)
if(MOVABLE_LIGHT)
AddComponent(/datum/component/overlay_lighting)
if(MOVABLE_LIGHT_DIRECTIONAL)
AddComponent(/datum/component/overlay_lighting, is_directional = TRUE)
/atom/movable/Destroy(force)
QDEL_NULL(proximity_monitor)
QDEL_NULL(language_holder)
QDEL_NULL(em_block)
unbuckle_all_mobs(force = TRUE)
if(loc)
//Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
CanAtmosPass = ATMOS_PASS_YES
air_update_turf(TRUE, FALSE)
loc.handle_atom_del(src)
if(opacity)
RemoveElement(/datum/element/light_blocking)
invisibility = INVISIBILITY_ABSTRACT
if(pulledby)
pulledby.stop_pulling()
if(orbiting)
orbiting.end_orbit(src)
orbiting = null
. = ..()
for(var/movable_content in contents)
qdel(movable_content)
LAZYCLEARLIST(client_mobs_in_contents)
moveToNullspace()
/atom/movable/proc/update_emissive_block()
if(blocks_emissive != EMISSIVE_BLOCK_GENERIC)
return
if(length(managed_vis_overlays))
for(var/a in managed_vis_overlays)
var/obj/effect/overlay/vis/vs
if(vs.plane == EMISSIVE_BLOCKER_PLANE)
SSvis_overlays.remove_vis_overlay(src, list(vs))
break
SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir)
/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction)
if(!direction)
direction = DOWN
if(!source)
source = get_turf(src)
if(!source)
return FALSE
if(!target)
target = get_step_multiz(source, direction)
if(!target)
return FALSE
return !(movement_type & FLYING) && has_gravity(source) && !throwing
/atom/movable/proc/onZImpact(turf/T, levels)
var/atom/highest = T
for(var/i in T.contents)
var/atom/A = i
if(!A.density)
continue
if(isobj(A) || ismob(A))
if(A.layer > highest.layer)
highest = A
INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2)
return TRUE
//For physical constraints to travelling up/down.
/atom/movable/proc/can_zTravel(turf/destination, direction)
var/turf/T = get_turf(src)
if(!T)
return FALSE
if(!direction)
if(!destination)
return FALSE
direction = get_dir(T, destination)
if(direction != UP && direction != DOWN)
return FALSE
if(!destination)
destination = get_step_multiz(src, direction)
if(!destination)
return FALSE
return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T)
/atom/movable/vv_edit_var(var_name, var_value)
var/static/list/banned_edits = list("step_x" = TRUE, "step_y" = TRUE, "step_size" = TRUE, "bounds" = TRUE)
var/static/list/careful_edits = list("bound_x" = TRUE, "bound_y" = TRUE, "bound_width" = TRUE, "bound_height" = TRUE)
if(banned_edits[var_name])
return FALSE //PLEASE no.
if((careful_edits[var_name]) && (var_value % world.icon_size) != 0)
return FALSE
switch(var_name)
if(NAMEOF(src, x))
var/turf/T = locate(var_value, y, z)
if(T)
admin_teleport(T)
return TRUE
return FALSE
if(NAMEOF(src, y))
var/turf/T = locate(x, var_value, z)
if(T)
admin_teleport(T)
return TRUE
return FALSE
if(NAMEOF(src, z))
var/turf/T = locate(x, y, var_value)
if(T)
admin_teleport(T)
return TRUE
return FALSE
if(NAMEOF(src, loc))
if(isatom(var_value) || isnull(var_value))
admin_teleport(var_value)
return TRUE
return FALSE
if(NAMEOF(src, anchored))
set_anchored(var_value)
. = TRUE
if(NAMEOF(src, pulledby))
set_pulledby(var_value)
. = TRUE
if(NAMEOF(src, glide_size))
set_glide_size(var_value)
. = TRUE
if(!isnull(.))
datum_flags |= DF_VAR_EDITED
return
return ..()
/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
if(QDELETED(AM))
return FALSE
if(!(AM.can_be_pulled(src, state, force)))
return FALSE
// If we're pulling something then drop what we're currently pulling and pull this instead.
if(pulling)
if(state == 0)
stop_pulling()
return FALSE
// Are we trying to pull something we are already pulling? Then enter grab cycle and end.
if(AM == pulling)
setGrabState(state)
if(istype(AM,/mob/living))
var/mob/living/AMob = AM
AMob.grabbedby(src)
return TRUE
stop_pulling()
SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force)
if(AM.pulledby)
log_combat(AM, AM.pulledby, "pulled from", src)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = AM
AM.set_pulledby(src)
setGrabState(state)
if(ismob(AM))
var/mob/M = AM
log_combat(src, M, "grabbed", addition="passive grab")
if(!supress_message)
M.visible_message("<span class='warning'>[src] grabs [M] passively.</span>", \
"<span class='danger'>[src] grabs you passively.</span>")
return TRUE
/atom/movable/proc/stop_pulling()
if(pulling)
pulling.set_pulledby(null)
setGrabState(GRAB_PASSIVE)
pulling = null
///Reports the event of the change in value of the pulledby variable.
/atom/movable/proc/set_pulledby(new_pulledby)
if(new_pulledby == pulledby)
return FALSE //null signals there was a change, be sure to return FALSE if none happened here.
. = pulledby
pulledby = new_pulledby
/atom/movable/proc/Move_Pulled(atom/A)
if(!pulling)
return FALSE
if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src))
stop_pulling()
return FALSE
if(isliving(pulling))
var/mob/living/L = pulling
if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
stop_pulling()
return FALSE
if(A == loc && pulling.density)
return FALSE
var/move_dir = get_dir(pulling.loc, A)
if(!Process_Spacemove(move_dir))
return FALSE
pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size)
return TRUE
/mob/living/Move_Pulled(atom/A)
. = ..()
if(!. || !isliving(A))
return
var/mob/living/L = A
set_pull_offsets(L, grab_state)
/atom/movable/proc/check_pulling()
if(pulling)
var/atom/movable/pullee = pulling
if(pullee && get_dist(src, pullee) > 1)
stop_pulling()
return
if(!isturf(loc))
stop_pulling()
return
if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.")
stop_pulling()
return
if(pulling.anchored || pulling.move_resist > move_force)
stop_pulling()
return
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
/atom/movable/proc/set_glide_size(target = 8)
SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
glide_size = target
for(var/m in buckled_mobs)
var/mob/buckled_mob = m
buckled_mob.set_glide_size(target)
////////////////////////////////////////
// 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, direct=0, glide_size_override = 0)
. = FALSE
if(!newloc || newloc == loc)
return
if(!direct)
direct = get_dir(src, newloc)
if(set_dir_on_move)
setDir(direct)
if(!loc.Exit(src, newloc))
return
if(!newloc.Enter(src, src.loc))
return
if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
return
// Past this is the point of no return
var/atom/oldloc = loc
var/area/oldarea = get_area(oldloc)
var/area/newarea = get_area(newloc)
loc = newloc
. = TRUE
oldloc.Exited(src, newloc)
if(oldarea != newarea)
oldarea.Exited(src, newloc)
for(var/i in oldloc)
if(i == src) // Multi tile objects
continue
var/atom/movable/thing = i
thing.Uncrossed(src)
newloc.Entered(src, oldloc)
if(oldarea != newarea)
newarea.Entered(src, oldloc)
for(var/i in loc)
if(i == src) // Multi tile objects
continue
var/atom/movable/thing = i
thing.Crossed(src)
////////////////////////////////////////
/atom/movable/Move(atom/newloc, direct, glide_size_override = 0)
var/atom/movable/pullee = pulling
var/turf/T = loc
if(!moving_from_pull)
check_pulling()
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
//Early override for some cases like diagonal movement
if(glide_size_override)
set_glide_size(glide_size_override)
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)
setDir(first_step_dir)
else if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(direct)
moving_diagonally = 0
return
if(!loc || (loc == oldloc && oldloc != newloc))
last_move = 0
return
if(.)
Moved(oldloc, direct)
if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
if(pulling.anchored)
stop_pulling()
else
var/pull_dir = get_dir(src, pulling)
//puller and pullee more than one tile away or in diagonal position
if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))
pulling.moving_from_pull = src
pulling.Move(T, get_dir(pulling, T), glide_size) //the pullee tries to reach our previous position
pulling.moving_from_pull = null
check_pulling()
//glide_size strangely enough can change mid movement animation and update correctly while the animation is playing
//This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs.
if(glide_size_override)
set_glide_size(glide_size_override)
last_move = direct
if(set_dir_on_move)
setDir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
return FALSE
//Called after a successful Move(). By this point, we've already moved
/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced)
if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
newtonian_move(Dir)
if (length(client_mobs_in_contents))
update_parallax_contents()
return TRUE
// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
// You probably want CanPass()
/atom/movable/Cross(atom/movable/AM)
. = TRUE
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSS, AM)
return CanPass(AM, AM.loc, TRUE)
//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
/atom/movable/Crossed(atom/movable/AM, oldloc)
SHOULD_CALL_PARENT(TRUE)
. = ..()
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
/atom/movable/Uncross(atom/movable/AM, atom/newloc)
. = ..()
if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS)
return FALSE
if(isturf(newloc) && !CheckExit(AM, newloc))
return FALSE
/atom/movable/Uncrossed(atom/movable/AM)
SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
/atom/movable/Bump(atom/A)
if(!A)
CRASH("Bump was called with no argument.")
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
. = ..()
if(!QDELETED(throwing))
throwing.hit_atom(A)
. = TRUE
if(QDELETED(A))
return
A.Bumped(src)
///Sets the anchored var and returns if it was sucessfully changed or not.
/atom/movable/proc/set_anchored(anchorvalue)
SHOULD_CALL_PARENT(TRUE)
if(anchored == anchorvalue)
return
. = anchored
anchored = anchorvalue
SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue)
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
if(destination)
. = doMove(destination)
else
CRASH("No valid destination passed into forceMove")
/atom/movable/proc/moveToNullspace()
return doMove(null)
/atom/movable/proc/doMove(atom/destination)
. = FALSE
if(destination)
if(pulledby)
pulledby.stop_pulling()
var/atom/oldloc = loc
var/same_loc = oldloc == destination
var/area/old_area = get_area(oldloc)
var/area/destarea = get_area(destination)
loc = destination
moving_diagonally = 0
if(!same_loc)
if(oldloc)
oldloc.Exited(src, destination)
if(old_area && old_area != destarea)
old_area.Exited(src, destination)
for(var/atom/movable/AM in oldloc)
AM.Uncrossed(src)
var/turf/oldturf = get_turf(oldloc)
var/turf/destturf = get_turf(destination)
var/old_z = (oldturf ? oldturf.z : null)
var/dest_z = (destturf ? destturf.z : null)
if (old_z != dest_z)
onTransitZ(old_z, dest_z)
destination.Entered(src, oldloc)
if(destarea && old_area != destarea)
destarea.Entered(src, oldloc)
for(var/atom/movable/AM in destination)
if(AM == src)
continue
AM.Crossed(src, oldloc)
Moved(oldloc, NONE, TRUE)
. = TRUE
//If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
else
. = TRUE
if (loc)
var/atom/oldloc = loc
var/area/old_area = get_area(oldloc)
oldloc.Exited(src, null)
if(old_area)
old_area.Exited(src, null)
loc = null
/atom/movable/proc/onTransitZ(old_z,new_z)
SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z)
for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care.
var/atom/movable/AM = item
AM.onTransitZ(old_z,new_z)
/**
* 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 0 to have src start/keep drifting in a no-grav area and 1 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
*/
/atom/movable/proc/Process_Spacemove(movement_dir = 0)
if(has_gravity(src))
return TRUE
if(pulledby && (pulledby.pulledby != src || moving_from_pull))
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
/atom/movable/proc/newtonian_move(direction)
if(!isturf(loc) || Process_Spacemove(0))
inertia_dir = 0
return FALSE
inertia_dir = direction
if(!direction)
return TRUE
inertia_last_loc = loc
SSspacedrift.processing[src] = src
return TRUE
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
set waitfor = FALSE
var/hitpush = TRUE
var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_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))) // in case a signal interceptor broke or deleted the thing before we could process our hit
return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum)
if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO))))
step(src, AM.dir)
..()
/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
return
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
///If this returns FALSE then callback will not be called.
/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_selected
var/datum/thrownthing/TT = new(src, target, get_turf(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)
TT.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
TT.dist_x = dist_x
TT.dist_y = dist_y
TT.dx = dx
TT.dy = dy
TT.diagonal_error = dist_x/2 - dist_y
TT.start_time = world.time
if(pulledby)
pulledby.stop_pulling()
if (quickstart && (throwing || SSthrowing.state == SS_RUNNING)) //Avoid stack overflow edgecases.
quickstart = FALSE
throwing = TT
if(spin)
SpinAnimation(5, 1)
SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
SSthrowing.processing[src] = TT
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
SSthrowing.currentrun[src] = TT
if (quickstart)
TT.tick()
/atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
if(!buckled_mob.Move(newloc, direct, glide_size_override))
forceMove(buckled_mob.loc)
last_move = buckled_mob.last_move
inertia_dir = last_move
buckled_mob.inertia_dir = last_move
return FALSE
return TRUE
/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE)
. = AM.force_pushed(src, force, direction)
if(!silent && .)
visible_message("<span class='warning'>[src] forcefully pushes against [AM]!</span>", "<span class='warning'>You forcefully push against [AM]!</span>")
/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE)
. = AM.move_crushed(src, force, direction)
if(!silent && .)
visible_message("<span class='danger'>[src] crushes past [AM]!</span>", "<span class='danger'>You crush [AM]!</span>")
/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
/atom/movable/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
if(mover in buckled_mobs)
return TRUE
/// Returns true or false to allow src to move through the blocker, mover has final say
/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
return blocker_opinion
/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/master_storage)
SEND_SIGNAL(src, COMSIG_STORAGE_EXITED, master_storage)
/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/master_storage)
SEND_SIGNAL(src, COMSIG_STORAGE_ENTERED, master_storage)
/atom/movable/proc/get_spacemove_backup()
var/atom/movable/dense_object_backup
for(var/A in orange(1, get_turf(src)))
if(isarea(A))
continue
else if(isturf(A))
var/turf/turf = A
if(!turf.density)
continue
return turf
else
var/atom/movable/AM = A
if(!AM.CanPass(src) || AM.density)
if(AM.anchored)
return AM
dense_object_backup = AM
break
. = dense_object_backup
///called when a mob resists while inside a container that is itself inside something.
/atom/movable/proc/relay_container_resist_act(mob/living/user, obj/O)
return
/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(A, visual_effect_icon, used_item)
if(A == src)
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
var/turn_dir = 1
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = 8
turn_dir = prob(50) ? -1 : 1
else if(direction & SOUTH)
pixel_y_diff = -8
turn_dir = prob(50) ? -1 : 1
if(direction & EAST)
pixel_x_diff = 8
else if(direction & WEST)
pixel_x_diff = -8
turn_dir = -1
var/matrix/initial_transform = matrix(transform)
var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING, flags = ANIMATION_PARALLEL)
/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item)
var/image/I
if(visual_effect_icon)
I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1)
else if(used_item)
I = image(icon = used_item, loc = A, layer = A.layer + 0.1)
I.plane = GAME_PLANE
// Scale the icon.
I.transform *= 0.4
// The icon should not rotate.
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
// Set the direction of the icon animation.
var/direction = get_dir(src, A)
if(direction & NORTH)
I.pixel_y = -12
else if(direction & SOUTH)
I.pixel_y = 12
if(direction & EAST)
I.pixel_x = -14
else if(direction & WEST)
I.pixel_x = 14
if(!direction) // Attacked self?!
I.pixel_z = 16
if(!I)
return
flick_overlay(I, GLOB.clients, 10)
// And animate the attack!
animate(I, alpha = 175, transform = matrix() * 0.75, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3)
animate(time = 1)
animate(alpha = 0, time = 3, easing = CIRCULAR_EASING|EASE_OUT)
/atom/movable/vv_get_dropdown()
. = ..()
. += "<option value='?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(src)]'>Follow</option>"
. += "<option value='?_src_=holder;[HrefToken()];admingetmovable=[REF(src)]'>Get</option>"
/atom/movable/proc/ex_check(ex_id)
if(!ex_id)
return TRUE
LAZYINITLIST(acted_explosions)
if(ex_id in acted_explosions)
return FALSE
acted_explosions += ex_id
return TRUE
/* Language procs
* Unless you are doing something very specific, these are the ones you want to use.
*/
/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one.
/atom/movable/proc/get_language_holder(get_minds = TRUE)
if(!language_holder)
language_holder = new initial_language_holder(src)
return language_holder
/// Grants the supplied language and sets omnitongue true.
/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.grant_language(language, understood, spoken, source)
/// Grants every language.
/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
var/datum/language_holder/LH = get_language_holder()
return LH.grant_all_languages(understood, spoken, grant_omnitongue, source)
/// Removes a single language.
/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_language(language, understood, spoken, source)
/// Removes every language and sets omnitongue false.
/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_all_languages(source, remove_omnitongue)
/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later.
/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.add_blocked_language(language, source)
/// Removes a language from the blocked language list.
/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_blocked_language(language, source)
/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language.
/atom/movable/proc/has_language(language, spoken = FALSE)
var/datum/language_holder/LH = get_language_holder()
return LH.has_language(language, spoken)
/// Checks if atom can speak the language.
/atom/movable/proc/can_speak_language(language)
var/datum/language_holder/LH = get_language_holder()
return LH.can_speak_language(language)
/// Returns the result of tongue specific limitations on spoken languages.
/atom/movable/proc/could_speak_language(language)
return TRUE
/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible.
/atom/movable/proc/get_selected_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_selected_language()
/// Gets a random understood language, useful for hallucinations and such.
/atom/movable/proc/get_random_understood_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_random_understood_language()
/// Gets a random spoken language, useful for forced speech and such.
/atom/movable/proc/get_random_spoken_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_random_spoken_language()
/// Copies all languages into the supplied atom/language holder. Source should be overridden when you
/// do not want the language overwritten by later atom updates or want to avoid blocked languages.
/atom/movable/proc/copy_languages(from_holder, source_override)
if(isatom(from_holder))
var/atom/movable/thing = from_holder
from_holder = thing.get_language_holder()
var/datum/language_holder/LH = get_language_holder()
return LH.copy_languages(from_holder, source_override)
/// Empties out the atom specific languages and updates them according to the current atoms language holder.
/// As a side effect, it also creates missing language holders in the process.
/atom/movable/proc/update_atom_languages()
var/datum/language_holder/LH = get_language_holder()
return LH.update_atom_languages(src)
/* End language procs */
//Returns an atom's power cell, if it has one. Overload for individual items.
/atom/movable/proc/get_cell()
return
/atom/movable/proc/can_be_pulled(user, grab_state, force)
if(src == user || !isturf(loc))
return FALSE
if(anchored || throwing)
return FALSE
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
return FALSE
return TRUE
/**
* Updates the grab state of the movable
*
* This exists to act as a hook for behaviour
*/
/atom/movable/proc/setGrabState(newstate)
if(newstate == grab_state)
return
SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate)
. = grab_state
grab_state = newstate
switch(grab_state) // Current state.
if(GRAB_PASSIVE)
REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher.
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
if(GRAB_AGGRESSIVE)
if(. >= GRAB_NECK) // Grab got downgraded.
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
else // Grab got upgraded from a passive one.
ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
if(GRAB_NECK, GRAB_KILL)
if(. <= GRAB_AGGRESSIVE)
ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
/**
* Adds the deadchat_plays component to this atom with simple movement commands.
*
* Returns the component added.
* Arguments:
* * mode - Either ANARCHY_MODE or DEMOCRACY_MODE passed to the deadchat_control component. See [/datum/component/deadchat_control] for more info.
* * cooldown - The cooldown between command inputs passed to the deadchat_control component. See [/datum/component/deadchat_control] for more info.
*/
/atom/movable/proc/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS)
return AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list(), cooldown)
/atom/movable/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION(VV_HK_DEADCHAT_PLAYS, "Start/Stop Deadchat Plays")
/atom/movable/vv_do_topic(list/href_list)
. = ..()
if(!.)
return
if(href_list[VV_HK_DEADCHAT_PLAYS] && check_rights(R_FUN))
if(alert(usr, "Allow deadchat to control [src] via chat commands?", "Deadchat Plays [src]", "Allow", "Cancel") == "Cancel")
return
// Alert is async, so quick sanity check to make sure we should still be doing this.
if(QDELETED(src))
return
// This should never happen, but if it does it should not be silent.
if(deadchat_plays() == COMPONENT_INCOMPATIBLE)
to_chat(usr, "<span class='warning'>Deadchat control not compatible with [src].</span>")
CRASH("deadchat_control component incompatible with object of type: [type]")
to_chat(usr, "<span class='notice'>Deadchat now control [src].</span>")
log_admin("[key_name(usr)] has added deadchat control to [src]")
message_admins("<span class='notice'>[key_name(usr)] has added deadchat control to [src]</span>")
/obj/item/proc/do_pickup_animation(atom/target)
set waitfor = FALSE
if(!istype(loc, /turf))
return
var/image/I = image(icon = src, loc = loc, layer = layer + 0.1)
I.plane = GAME_PLANE
I.transform *= 0.75
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
var/turf/T = get_turf(src)
var/direction
var/to_x = target.base_pixel_x
var/to_y = target.base_pixel_y
if(!QDELETED(T) && !QDELETED(target))
direction = get_dir(T, target)
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 += 16
flick_overlay(I, GLOB.clients, 6)
var/matrix/M = new
M.Turn(pick(-30, 30))
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
animate(I, alpha = 0, transform = matrix(), time = 1)
/**
* A wrapper for setDir that should only be able to fail by living mobs.
*
* Called from [/atom/movable/proc/keyLoop], this exists to be overwritten by living mobs with a check to see if we're actually alive enough to change directions
*/
/atom/movable/proc/keybind_face_direction(direction)
setDir(direction)