mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
993 lines
32 KiB
Plaintext
993 lines
32 KiB
Plaintext
/atom/movable
|
|
layer = OBJ_LAYER
|
|
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
|
|
var/mob/pulledby = null
|
|
var/initial_language_holder = /datum/language_holder
|
|
var/datum/language_holder/language_holder
|
|
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
|
|
var/pass_flags = NONE
|
|
/// If false makes CanPass call 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
|
|
glide_size = 8
|
|
appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
|
|
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
|
|
var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc.
|
|
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
|
|
|
|
/// 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
|
|
|
|
/// The degree of thermal insulation that mobs in list/contents have from the external environment, between 0 and 1
|
|
var/contents_thermal_insulation = 0
|
|
/// The degree of pressure protection that mobs in list/contents have from the external environment, between 0 and 1
|
|
var/contents_pressure_protection = 0
|
|
|
|
|
|
/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
|
|
|
|
/atom/movable/Destroy()
|
|
QDEL_NULL(em_block)
|
|
return ..()
|
|
|
|
/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)
|
|
throw_impact(highest)
|
|
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", "step_y", "step_size", "bounds")
|
|
var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height")
|
|
if(var_name in banned_edits)
|
|
return FALSE //PLEASE no.
|
|
if((var_name in careful_edits) && (var_value % world.icon_size) != 0)
|
|
return FALSE
|
|
switch(var_name)
|
|
if("x")
|
|
var/turf/T = locate(var_value, y, z)
|
|
if(T)
|
|
forceMove(T)
|
|
return TRUE
|
|
return FALSE
|
|
if("y")
|
|
var/turf/T = locate(x, var_value, z)
|
|
if(T)
|
|
forceMove(T)
|
|
return TRUE
|
|
return FALSE
|
|
if("z")
|
|
var/turf/T = locate(x, y, var_value)
|
|
if(T)
|
|
forceMove(T)
|
|
return TRUE
|
|
return FALSE
|
|
if("loc")
|
|
if(istype(var_value, /atom))
|
|
forceMove(var_value)
|
|
return TRUE
|
|
else if(isnull(var_value))
|
|
moveToNullspace()
|
|
return TRUE
|
|
return FALSE
|
|
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)
|
|
grab_state = state
|
|
if(istype(AM,/mob/living))
|
|
var/mob/living/AMob = AM
|
|
AMob.grabbedby(src)
|
|
return TRUE
|
|
stop_pulling()
|
|
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.pulledby = src
|
|
grab_state = state
|
|
if(ismob(AM))
|
|
var/mob/M = AM
|
|
log_combat(src, M, "grabbed", addition="passive grab")
|
|
if(!supress_message)
|
|
visible_message(span_warning("[src] has grabbed [M] passively!"))
|
|
return TRUE
|
|
|
|
/atom/movable/proc/stop_pulling()
|
|
if(pulling)
|
|
pulling.pulledby = null
|
|
var/mob/living/ex_pulled = pulling
|
|
pulling = null
|
|
grab_state = 0
|
|
if(isliving(ex_pulled))
|
|
var/mob/living/L = ex_pulled
|
|
L.update_mobility()// mob gets up if it was lyng down in a chokehold
|
|
|
|
/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/atom/movable/AM in buckled_mobs)
|
|
AM.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)
|
|
setDir(direct)
|
|
|
|
// yogs start - multi tile object handling
|
|
if(bound_width != world.icon_size || bound_height != world.icon_size)
|
|
var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc)
|
|
if(!newlocs)
|
|
return // we're trying to cross into the edge of space
|
|
var/bothturfs = isturf(newloc) && isturf(loc)
|
|
var/dx = bothturfs ? newloc.x - loc.x : 0
|
|
var/dy = bothturfs ? newloc.y - loc.y : 0
|
|
var/dz = bothturfs ? newloc.z - loc.z : 0
|
|
for(var/atom/A in (locs - newlocs))
|
|
if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc))
|
|
return
|
|
for(var/atom/A in (newlocs - locs))
|
|
if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc))
|
|
return
|
|
else
|
|
if(!loc.Exit(src, newloc))
|
|
return
|
|
|
|
if(!newloc.Enter(src, src.loc))
|
|
return
|
|
// yogs end
|
|
|
|
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(!.)
|
|
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
|
|
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()
|
|
|
|
SSdemo.mark_dirty(src)
|
|
return TRUE
|
|
|
|
/atom/movable/Destroy(force)
|
|
QDEL_NULL(proximity_monitor)
|
|
QDEL_NULL(language_holder)
|
|
|
|
unbuckle_all_mobs(force=1)
|
|
|
|
. = ..()
|
|
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()
|
|
loc.handle_atom_del(src)
|
|
for(var/atom/movable/AM in contents)
|
|
qdel(AM)
|
|
moveToNullspace()
|
|
invisibility = INVISIBILITY_ABSTRACT
|
|
if(pulledby)
|
|
pulledby.stop_pulling()
|
|
|
|
if(orbiting)
|
|
orbiting.end_orbit(src)
|
|
orbiting = null
|
|
|
|
// 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)
|
|
|
|
/atom/movable/proc/forceMove(atom/destination)
|
|
. = FALSE
|
|
if(destination)
|
|
var/turf/new_turf = get_turf(destination)
|
|
if(new_turf && ismob(src))
|
|
var/mob/M = src
|
|
if(is_secret_level(new_turf.z) && !M.client?.holder)
|
|
return
|
|
|
|
. = 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)
|
|
|
|
/atom/movable/proc/setMovetype(newval)
|
|
movement_type = newval
|
|
|
|
//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)
|
|
return 1
|
|
|
|
if(throwing)
|
|
return 1
|
|
|
|
if(!isturf(loc))
|
|
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
|
|
|
|
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
|
set waitfor = 0
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
|
|
return hit_atom.hitby(src, throwingdatum=throwingdatum)
|
|
|
|
/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, quickstart = TRUE)
|
|
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
|
|
return
|
|
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force)
|
|
|
|
/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, quickstart = TRUE) //If this returns FALSE then callback will not be called.
|
|
. = FALSE
|
|
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.movement_delay()
|
|
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/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.force = force
|
|
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)
|
|
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_warning("[src] forcefully pushes against [AM]!"), span_warning("You forcefully push against [AM]!"))
|
|
|
|
/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_danger("[src] crushes past [AM]!"), span_danger("You crush [AM]!"))
|
|
|
|
/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
|
|
return FALSE
|
|
|
|
/atom/movable/CanAllowThrough(atom/movable/mover, turf/target)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
. = ..()
|
|
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/S)
|
|
return
|
|
|
|
// 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/S)
|
|
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
|
|
|
|
//called when a mob resists while inside a container that is itself inside something.
|
|
/atom/movable/proc/relay_container_resist(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/direction = get_dir(src, A)
|
|
if(direction & NORTH)
|
|
pixel_y_diff = 8
|
|
else if(direction & SOUTH)
|
|
pixel_y_diff = -8
|
|
|
|
if(direction & EAST)
|
|
pixel_x_diff = 8
|
|
else if(direction & WEST)
|
|
pixel_x_diff = -8
|
|
|
|
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 0.2 SECONDS)
|
|
animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 0.2 SECONDS)
|
|
|
|
/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
|
|
|
|
flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second
|
|
|
|
// And animate the attack!
|
|
var/t_color = "#ffffff" //yogs start
|
|
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) //yogs end
|
|
|
|
/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
|
|
|
|
//TODO: Better floating
|
|
/atom/movable/proc/float(on)
|
|
if(throwing)
|
|
return
|
|
if(on && !(movement_type & FLOATING))
|
|
animate(src, pixel_y = pixel_y + 2, time = 1 SECONDS, loop = -1)
|
|
sleep(1 SECONDS)
|
|
animate(src, pixel_y = pixel_y - 2, time = 1 SECONDS, loop = -1)
|
|
setMovetype(movement_type | FLOATING)
|
|
else if (!on && (movement_type & FLOATING))
|
|
animate(src, pixel_y = initial(pixel_y), time = 1 SECONDS)
|
|
setMovetype(movement_type & ~FLOATING)
|
|
|
|
/* 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
|
|
|
|
/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 = 0
|
|
var/to_y = 0
|
|
|
|
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 = 0.3 SECONDS, transform = M, easing = CUBIC_EASING)
|
|
sleep(0.1 SECONDS)
|
|
animate(I, alpha = 0, transform = matrix(), time = 0.1 SECONDS)
|