Files
CHOMPStation2/code/game/atoms_movable.dm
Leshana c837078105 Replaced "area" shuttles with "landmark" shuttles.
Largely ported from the work done at Baystation in https://github.com/Baystation12/Baystation12/pull/17460 and later commits.

 - Shuttles no longer require a separate area for each location they jump to.
   Instead destinations are indicated by landmark objects, which are not necessarily exclusive to that shuttle.
   This means that more than one shuttle could use the same docking port (not at the same time of course).
 - Enhanced shuttle control computers to use nanoui if they didn't.
 - Organizes shuttle datum code a bit better so there is less re-inventing the wheel in subtypes.
 - Allows the possibility of shuttles (or destinations) that start on late-loaded maps.
 - Deprecate the "extra" shuttle areas that are no longer needed and update shuttle areas in unit tests

This all required a bit of infrastructure improvements.

 - ChangeArea proc, for changing the area of a turf.
 - Fixed lighting overlays actually being able to be destroyed.
 - Added a few utility macros and procs.
 - Added "turf translation" procs which are like move_contents_to but more flexible.
2020-03-05 10:29:08 -05:00

519 lines
15 KiB
Plaintext

/atom/movable
layer = OBJ_LAYER
appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER //VOREStation Edit
var/last_move = null
var/anchored = 0
// var/elevation = 2 - not used anywhere
var/moving_diagonally
var/move_speed = 10
var/l_move_time = 1
var/m_flag = 1
var/throwing = 0
var/thrower
var/turf/throw_source = null
var/throw_speed = 2
var/throw_range = 7
var/moved_recently = 0
var/mob/pulledby = null
var/item_state = null // Used to specify the item state for the on-mob overlays.
var/icon_scale_x = 1 // Used to scale icons up or down horizonally in update_transform().
var/icon_scale_y = 1 // Used to scale icons up or down vertically in update_transform().
var/icon_rotation = 0 // Used to rotate icons in update_transform()
var/old_x = 0
var/old_y = 0
var/datum/riding/riding_datum //VOREStation Add - Moved from /obj/vehicle
var/does_spin = TRUE // Does the atom spin when thrown (of course it does :P)
var/movement_type = NONE
/atom/movable/Destroy()
. = ..()
if(reagents)
qdel(reagents)
reagents = null
for(var/atom/movable/AM in contents)
qdel(AM)
var/turf/un_opaque
if(opacity && isturf(loc))
un_opaque = loc
moveToNullspace()
if(un_opaque)
un_opaque.recalc_atom_opacity()
if (pulledby)
if (pulledby.pulling == src)
pulledby.pulling = null
pulledby = null
QDEL_NULL(riding_datum) //VOREStation Add
/atom/movable/vv_edit_var(var_name, var_value)
if(GLOB.VVpixelmovement[var_name]) //Pixel movement is not yet implemented, changing this will break everything irreversibly.
return FALSE
return ..()
////////////////////////////////////////
// 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)
. = FALSE
if(!newloc || newloc == loc)
return
if(!direct)
direct = get_dir(src, newloc)
set_dir(direct)
if(!loc.Exit(src, newloc))
return
if(!newloc.Enter(src, src.loc))
return
if(!check_multi_tile_move_density_dir(direct, locs)) // We're big, and we can't move that way.
return
// Past this is the point of no return
if(!locs || locs.len <= 1) // We're not a multi-tile object.
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)
else if(newloc) // We're a multi-tile object.
. = doMove(newloc)
//
////////////////////////////////////////
/atom/movable/Move(atom/newloc, direct = 0)
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
if(loc != newloc)
if(!direct)
direct = get_dir(oldloc, 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(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)
//Polaris stuff
move_speed = world.time - l_move_time
l_move_time = world.time
m_flag = 1
//End
last_move = direct
set_dir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s)
return FALSE
//VOREStation Add
else if(. && riding_datum)
riding_datum.handle_vehicle_layer()
riding_datum.handle_vehicle_offsets()
//VOREStation Add End
//Called after a successful Move(). By this point, we've already moved
/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
//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
return CanPass(AM, loc)
/atom/movable/CanPass(atom/movable/mover, turf/target)
. = ..()
if(locs && locs.len >= 2) // If something is standing on top of us, let them pass.
if(mover.loc in locs)
. = TRUE
return .
//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
/atom/movable/Crossed(atom/movable/AM, oldloc)
return
/atom/movable/Uncross(atom/movable/AM, atom/newloc)
. = ..()
if(isturf(newloc) && !CheckExit(AM, newloc))
return FALSE
/atom/movable/Bump(atom/A)
if(!A)
CRASH("Bump was called with no argument.")
. = ..()
if(throwing)
throw_impact(A)
throwing = 0
if(QDELETED(A))
return
A.Bumped(src)
A.last_bumped = world.time
/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)
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)
// Break pulling if we are too far to pull now.
if(pulledby && (pulledby.z != src.z || get_dist(pulledby, src) > 1))
pulledby.stop_pulling()
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)
GLOB.z_moved_event.raise_event(src, 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 when src is thrown into hit_atom
/atom/movable/proc/throw_impact(atom/hit_atom, var/speed)
if(istype(hit_atom,/mob/living))
var/mob/living/M = hit_atom
if(M.buckled == src)
return // Don't hit the thing we're buckled to.
M.hitby(src,speed)
else if(isobj(hit_atom))
var/obj/O = hit_atom
if(!O.anchored)
step(O, src.last_move)
O.hitby(src,speed)
else if(isturf(hit_atom))
src.throwing = 0
var/turf/T = hit_atom
T.hitby(src,speed)
//decided whether a movable atom being thrown can pass through the turf it is in.
/atom/movable/proc/hit_check(var/speed)
if(src.throwing)
for(var/atom/A in get_turf(src))
if(A == src) continue
if(istype(A,/mob/living))
if(A:lying) continue
src.throw_impact(A,speed)
if(isobj(A))
if(!A.density || A.throwpass)
continue
// Special handling of windows, which are dense but block only from some directions
if(istype(A, /obj/structure/window))
var/obj/structure/window/W = A
if (!W.is_fulltile() && !(turn(src.last_move, 180) & A.dir))
continue
// Same thing for (closed) windoors, which have the same problem
else if(istype(A, /obj/machinery/door/window) && !(turn(src.last_move, 180) & A.dir))
continue
src.throw_impact(A,speed)
/atom/movable/proc/throw_at(atom/target, range, speed, thrower)
if(!target || !src)
return 0
if(target.z != src.z)
return 0
//use a modified version of Bresenham's algorithm to get from the atom's current position to that of the target
src.throwing = 1
src.thrower = thrower
src.throw_source = get_turf(src) //store the origin turf
src.pixel_z = 0
if(usr)
if(HULK in usr.mutations)
src.throwing = 2 // really strong throw!
var/dist_travelled = 0
var/dist_since_sleep = 0
var/area/a = get_area(src.loc)
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
var/dx
if (target.x > src.x)
dx = EAST
else
dx = WEST
var/dy
if (target.y > src.y)
dy = NORTH
else
dy = SOUTH
var/error
var/major_dir
var/major_dist
var/minor_dir
var/minor_dist
if(dist_x > dist_y)
error = dist_x/2 - dist_y
major_dir = dx
major_dist = dist_x
minor_dir = dy
minor_dist = dist_y
else
error = dist_y/2 - dist_x
major_dir = dy
major_dist = dist_y
minor_dir = dx
minor_dist = dist_x
while(src && target && src.throwing && istype(src.loc, /turf) \
&& ((abs(target.x - src.x)+abs(target.y - src.y) > 0 && dist_travelled < range) \
|| (a && a.has_gravity == 0) \
|| istype(src.loc, /turf/space)))
// only stop when we've gone the whole distance (or max throw range) and are on a non-space tile, or hit something, or hit the end of the map, or someone picks it up
var/atom/step
if(error >= 0)
step = get_step(src, major_dir)
error -= minor_dist
else
step = get_step(src, minor_dir)
error += major_dist
if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
break
src.Move(step)
hit_check(speed)
dist_travelled++
dist_since_sleep++
if(dist_since_sleep >= speed)
dist_since_sleep = 0
sleep(1)
a = get_area(src.loc)
// and yet it moves
if(src.does_spin)
src.SpinAnimation(speed = 4, loops = 1)
//done throwing, either because it hit something or it finished moving
if(isobj(src)) src.throw_impact(get_turf(src),speed)
src.throwing = 0
src.thrower = null
src.throw_source = null
fall()
//Overlays
/atom/movable/overlay
var/atom/master = null
anchored = 1
/atom/movable/overlay/New()
for(var/x in src.verbs)
src.verbs -= x
..()
/atom/movable/overlay/attackby(a, b)
if (src.master)
return src.master.attackby(a, b)
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 using_map.sealed_levels)
return
if(using_map.use_overmap)
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(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear
var/datum/game_mode/nuclear/G = ticker.mode
G.check_nuke_disks()
spawn(0)
if(loc) loc.Entered(src)
//by default, transition randomly to another zlevel
/atom/movable/proc/get_transit_zlevel()
var/list/candidates = using_map.accessible_z_levels.Copy()
candidates.Remove("[src.z]")
if(!candidates.len)
return null
return text2num(pickweight(candidates))
/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()
// Called when touching a lava tile.
/atom/movable/proc/lava_act()
fire_act(null, 10000, 1000)