Files
Paradise/code/game/atoms_movable.dm
Fox McCloud 23164e354b conflicts
2021-09-06 04:06:47 -04:00

593 lines
18 KiB
Plaintext

/atom/movable
layer = 3
appearance_flags = TILE_BOUND
glide_size = 8 // Default, adjusted when mobs move based on their movement delays
var/last_move = null
var/anchored = FALSE
var/move_resist = MOVE_RESIST_DEFAULT
var/move_force = MOVE_FORCE_DEFAULT
var/pull_force = PULL_FORCE_DEFAULT
// var/elevation = 2 - not used anywhere
var/move_speed = 10
var/l_move_time = 1
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
var/no_spin = FALSE
var/no_spin_thrown = FALSE
var/moved_recently = FALSE
var/mob/pulledby = null
var/atom/movable/pulling
/// Face towards the atom while pulling it
var/face_while_pulling = FALSE
var/throwforce = 0
var/canmove = TRUE
var/inertia_dir = 0
var/atom/inertia_last_loc
var/inertia_moving = 0
var/inertia_next_move = 0
var/inertia_move_delay = 5
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/list/client_mobs_in_contents
/atom/movable/attempt_init(loc, ...)
var/turf/T = get_turf(src)
if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && GLOB.space_manager.is_zlevel_dirty(T.z))
GLOB.space_manager.postpone_init(T.z, src)
return
. = ..()
/atom/movable/Destroy()
unbuckle_all_mobs(force = TRUE)
. = ..()
if(loc)
loc.handle_atom_del(src)
for(var/atom/movable/AM in contents)
qdel(AM)
LAZYCLEARLIST(client_mobs_in_contents)
loc = null
if(pulledby)
pulledby.stop_pulling()
if(orbiting)
stop_orbit()
//Returns an atom's power cell, if it has one. Overload for individual items.
/atom/movable/proc/get_cell()
return
/atom/movable/proc/start_pulling(atom/movable/AM, state, force = pull_force, show_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)
if(isliving(AM))
var/mob/living/AMob = AM
AMob.grabbedby(src)
return TRUE
stop_pulling()
if(AM.pulledby)
add_attack_logs(AM, AM.pulledby, "pulled from", ATKLOG_ALMOSTALL)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = AM
AM.pulledby = src
if(ismob(AM))
var/mob/M = AM
add_attack_logs(src, M, "passively grabbed", ATKLOG_ALMOSTALL)
if(show_message)
visible_message("<span class='warning'>[src] has grabbed [M] passively!</span>")
return TRUE
/atom/movable/proc/stop_pulling()
if(pulling)
pulling.pulledby = null
var/mob/living/ex_pulled = pulling
pulling = null
pulledby = null
if(isliving(ex_pulled))
var/mob/living/L = ex_pulled
L.update_canmove()// mob gets up if it was lyng down in a chokehold
/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/can_be_pulled(user, grab_state, force, show_message = FALSE)
if(src == user || !isturf(loc))
return FALSE
if(anchored || move_resist == INFINITY)
if(show_message)
to_chat(user, "<span class='warning'>[src] appears to be anchored to the ground!</span>")
return FALSE
if(throwing)
return FALSE
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
if(show_message)
to_chat(user, "<span class='warning'>[src] is too heavy to pull!</span>")
return FALSE
return TRUE
// Used in shuttle movement and AI eye stuff.
// Primarily used to notify objects being moved by a shuttle/bluespace fuckup.
/atom/movable/proc/setLoc(T, teleported=0)
loc = T
/atom/movable/Move(atom/newloc, direct = 0, movetime)
if(!loc || !newloc) return 0
var/atom/oldloc = loc
if(loc != newloc)
glide_for(movetime)
if(!(direct & (direct - 1))) //Cardinal move
. = ..(newloc, direct) // don't pass up movetime
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(!.)
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)
last_move = direct
src.move_speed = world.time - src.l_move_time
src.l_move_time = world.time
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, movetime)) //movement failed due to buckled mob
. = 0
// Called after a successful Move(). By this point, we've already moved
/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
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()
var/datum/light_source/L
var/thing
for (thing in light_sources) // Cycle through the light sources on this atom and tell them to update.
L = thing
L.source_atom.update_light()
return TRUE
// Change glide size for the duration of one movement
/atom/movable/proc/glide_for(movetime)
if(movetime)
glide_size = world.icon_size/max(DS2TICKS(movetime), 1)
spawn(movetime)
glide_size = initial(glide_size)
else
glide_size = initial(glide_size)
// Previously known as HasEntered()
// This is automatically called when something enters your square
/atom/movable/Crossed(atom/movable/AM, oldloc)
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src)
/atom/movable/Uncrossed(atom/movable/AM)
SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump().
if(A && yes)
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
if(throwing)
throwing.hit_atom(A)
. = 1
if(!A || QDELETED(A))
return
A.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
var/turf/old_loc = loc
loc = destination
moving_diagonally = 0
if(old_loc)
old_loc.Exited(src, destination)
for(var/atom/movable/AM in old_loc)
AM.Uncrossed(src)
if(destination)
destination.Entered(src)
for(var/atom/movable/AM in destination)
if(AM == src)
continue
AM.Crossed(src, old_loc)
var/turf/oldturf = get_turf(old_loc)
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)
Moved(old_loc, NONE)
return 1
/atom/movable/proc/onTransitZ(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)
/mob/living/forceMove(atom/destination)
if(buckled)
addtimer(CALLBACK(src, .proc/check_buckled), 1, TIMER_UNIQUE)
if(has_buckled_mobs())
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
addtimer(CALLBACK(buckled_mob, .proc/check_buckled), 1, TIMER_UNIQUE)
if(pulling)
addtimer(CALLBACK(src, .proc/check_pull), 1, TIMER_UNIQUE)
. = ..()
if(client)
reset_perspective(destination)
update_canmove() //if the mob was asleep inside a container and then got forceMoved out we need to make them fall.
update_runechat_msg_location()
//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() below
//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/Move() in mob_movement.dm
//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 1
if(pulledby && !pulledby.pulling)
return 1
if(throwing)
return 1
if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
return 1
return 0
/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity
if(!loc || Process_Spacemove(0))
inertia_dir = 0
return 0
inertia_dir = direction
if(!direction)
return 1
inertia_last_loc = loc
SSspacedrift.processing[src] = src
return 1
//called when src is thrown into hit_atom
/atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum)
set waitfor = 0
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
if(!QDELETED(hit_atom))
return hit_atom.hitby(src)
/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/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY)
if(!target || (flags & NODROP) || speed <= 0)
return 0
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.movement_delay()
if(!user_momentum) // no movement_delay, this means they move once per byond tick, let's 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.
var/datum/thrownthing/TT = new()
TT.thrownthing = src
TT.target = target
TT.target_turf = get_turf(target)
TT.init_dir = get_dir(src, target)
TT.maxrange = range
TT.speed = speed
TT.thrower = thrower
TT.diagonals_first = diagonals_first
TT.callback = callback
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()
throwing = TT
if(spin && !no_spin && !no_spin_thrown)
SpinAnimation(5, 1)
SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
SSthrowing.processing[src] = TT
TT.tick()
return TRUE
//Overlays
/atom/movable/overlay
var/atom/master = null
anchored = TRUE
simulated = FALSE
/atom/movable/overlay/New()
. = ..()
verbs.Cut()
return
/atom/movable/overlay/attackby(a, b, c)
if(master)
return master.attackby(a, b, c)
/atom/movable/overlay/attack_hand(a, b, c)
if(master)
return master.attack_hand(a, b, c)
/atom/movable/proc/handle_buckled_mob_movement(newloc,direct,movetime)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
if(!buckled_mob.Move(newloc, direct, movetime))
forceMove(buckled_mob.loc)
last_move = buckled_mob.last_move
inertia_dir = last_move
buckled_mob.inertia_dir = last_move
return 0
return 1
/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/CanPass(atom/movable/mover, turf/target, height=1.5)
if(mover in buckled_mobs)
return 1
return ..()
/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
/atom/movable/proc/transfer_prints_to(atom/movable/target = null, overwrite = FALSE)
if(!target)
return
if(overwrite)
target.fingerprints = fingerprints
target.fingerprintshidden = fingerprintshidden
else
target.fingerprints += fingerprints
target.fingerprintshidden += fingerprintshidden
target.fingerprintslast = fingerprintslast
/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(5 * turn_dir)
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform = rotated_transform, time = 0.1 SECONDS, easing = CUBIC_EASING)
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform = initial_transform, time = 0.2 SECONDS, easing = SINE_EASING)
/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.75
// 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 = -16
else if(direction & SOUTH)
I.pixel_y = 16
if(direction & EAST)
I.pixel_x = -16
else if(direction & WEST)
I.pixel_x = 16
if(!direction) // Attacked self?!
I.pixel_z = 16
if(!I)
return
// Who can see the attack?
var/list/viewing = list()
for(var/mob/M in viewers(A))
if(M.client && M.client.prefs.toggles2 & PREFTOGGLE_2_ITEMATTACK)
viewing |= M.client
I.appearance_flags |= RESET_TRANSFORM | KEEP_APART
flick_overlay(I, viewing, 7) // 7 ticks/half a second
// And animate the attack!
var/t_color = "#ffffff"
if(ismob(src) && ismob(A) && !used_item)
var/mob/M = src
t_color = M.a_intent == INTENT_HARM ? "#ff0000" : "#ffffff"
animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 0.3 SECONDS, color = t_color)
animate(time = 0.1 SECONDS)
animate(alpha = 0, time = 0.3 SECONDS, easing = CIRCULAR_EASING | EASE_OUT)
/atom/movable/proc/portal_destroyed(obj/effect/portal/P)
return
/atom/movable/proc/decompile_act(obj/item/matter_decompiler/C, mob/user) // For drones to decompile mobs and objs. See drone for an example.
return FALSE