mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
Basic multiZ mob movement procs (Observers can always move)
Open space, showing what things are below it, and everything that entails. No complex support for dynamic generation just yet.
Decide how openspace should look :/
Atoms falling through open space (NO MOB SUPPORT/DAMAGE/ANYTHING YET.)
//CANCELLED FOR ANOTHER PR - [ ] Being able to see upwards? Downwards is going to be handled by open space.
MultiZ tile atmospherics - EDIT: Working demo! https://puu.sh/B7mUs/3f6274740f.mp4
Bugtest the heck out of this trainwreck.
896 lines
28 KiB
Plaintext
896 lines
28 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_yell = "yells"
|
|
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 = 0
|
|
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 // 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
|
|
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
|
|
|
|
/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")
|
|
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 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
|
|
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
|
|
if(pulling.anchored || !pulling.Adjacent(src))
|
|
stop_pulling()
|
|
return
|
|
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
|
|
if(A == loc && pulling.density)
|
|
return
|
|
if(!Process_Spacemove(get_dir(pulling.loc, A)))
|
|
return
|
|
step(pulling, get_dir(pulling.loc, A))
|
|
|
|
/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)
|
|
stop_pulling()
|
|
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)
|
|
setDir(direct)
|
|
|
|
if(!loc.Exit(src, newloc))
|
|
return
|
|
|
|
if(!newloc.Enter(src, src.loc))
|
|
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)
|
|
var/atom/movable/pullee = pulling
|
|
var/turf/T = loc
|
|
if(pulling)
|
|
if(pullee && get_dist(src, pullee) > 1)
|
|
stop_pulling()
|
|
|
|
if(pullee && pullee.loc != loc && !isturf(pullee.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()
|
|
if(!loc || !newloc)
|
|
return FALSE
|
|
var/atom/oldloc = loc
|
|
|
|
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) //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.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position
|
|
if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up
|
|
stop_pulling()
|
|
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()
|
|
|
|
|
|
last_move = direct
|
|
setDir(direct)
|
|
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //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)
|
|
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
|
|
|
|
/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(TRUE)
|
|
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)
|
|
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)
|
|
. = 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)
|
|
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) //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
|
|
TT.tick()
|
|
|
|
/atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
|
|
for(var/m in buckled_mobs)
|
|
var/mob/living/buckled_mob = m
|
|
if(!buckled_mob.Move(newloc, direct))
|
|
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)
|
|
if(mover in buckled_mobs)
|
|
return 1
|
|
return ..()
|
|
|
|
// 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 = 2)
|
|
animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 2)
|
|
|
|
/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 = 3, color = t_color) //yogs end
|
|
|
|
/atom/movable/vv_get_dropdown()
|
|
. = ..()
|
|
. -= "Jump to"
|
|
.["Follow"] = "?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(src)]"
|
|
.["Get"] = "?_src_=holder;[HrefToken()];admingetmovable=[REF(src)]"
|
|
|
|
/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 = 10, loop = -1)
|
|
sleep(10)
|
|
animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1)
|
|
setMovetype(movement_type | FLOATING)
|
|
else if (!on && (movement_type & FLOATING))
|
|
animate(src, pixel_y = initial(pixel_y), time = 10)
|
|
setMovetype(movement_type & ~FLOATING)
|
|
|
|
/* Language procs */
|
|
/atom/movable/proc/get_language_holder(shadow=TRUE)
|
|
if(language_holder)
|
|
return language_holder
|
|
else
|
|
language_holder = new initial_language_holder(src)
|
|
return language_holder
|
|
|
|
/atom/movable/proc/grant_language(datum/language/dt, body = FALSE)
|
|
var/datum/language_holder/H = get_language_holder(!body)
|
|
H.grant_language(dt, body)
|
|
|
|
/atom/movable/proc/grant_all_languages(omnitongue=FALSE)
|
|
var/datum/language_holder/H = get_language_holder()
|
|
H.grant_all_languages(omnitongue)
|
|
|
|
/atom/movable/proc/get_random_understood_language()
|
|
var/datum/language_holder/H = get_language_holder()
|
|
. = H.get_random_understood_language()
|
|
|
|
/atom/movable/proc/remove_language(datum/language/dt, body = FALSE)
|
|
var/datum/language_holder/H = get_language_holder(!body)
|
|
H.remove_language(dt, body)
|
|
|
|
/atom/movable/proc/remove_all_languages()
|
|
var/datum/language_holder/H = get_language_holder()
|
|
H.remove_all_languages()
|
|
|
|
/atom/movable/proc/has_language(datum/language/dt)
|
|
var/datum/language_holder/H = get_language_holder()
|
|
. = H.has_language(dt)
|
|
|
|
/atom/movable/proc/copy_known_languages_from(thing, replace=FALSE)
|
|
var/datum/language_holder/H = get_language_holder()
|
|
. = H.copy_known_languages_from(thing, replace)
|
|
|
|
// Whether an AM can speak in a language or not, independent of whether
|
|
// it KNOWS the language
|
|
/atom/movable/proc/could_speak_in_language(datum/language/dt)
|
|
. = TRUE
|
|
|
|
/atom/movable/proc/can_speak_in_language(datum/language/dt)
|
|
var/datum/language_holder/H = get_language_holder()
|
|
|
|
if(!H.has_language(dt))
|
|
return FALSE
|
|
else if(H.omnitongue)
|
|
return TRUE
|
|
else if(could_speak_in_language(dt) && (!H.only_speaks_language || H.only_speaks_language == dt))
|
|
return TRUE
|
|
else
|
|
return FALSE
|
|
|
|
/atom/movable/proc/get_default_language()
|
|
// if no language is specified, and we want to say() something, which
|
|
// language do we use?
|
|
var/datum/language_holder/H = get_language_holder()
|
|
|
|
if(H.selected_default_language)
|
|
if(can_speak_in_language(H.selected_default_language))
|
|
return H.selected_default_language
|
|
else
|
|
H.selected_default_language = null
|
|
|
|
|
|
var/datum/language/chosen_langtype
|
|
var/highest_priority
|
|
|
|
for(var/lt in H.languages)
|
|
var/datum/language/langtype = lt
|
|
if(!can_speak_in_language(langtype))
|
|
continue
|
|
|
|
var/pri = initial(langtype.default_priority)
|
|
if(!highest_priority || (pri > highest_priority))
|
|
chosen_langtype = langtype
|
|
highest_priority = pri
|
|
|
|
H.selected_default_language = .
|
|
. = chosen_langtype
|
|
|
|
/* End language procs */
|
|
/atom/movable/proc/ConveyorMove(movedir)
|
|
set waitfor = FALSE
|
|
if(!anchored && has_gravity())
|
|
step(src, movedir)
|
|
|
|
//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 = 3, transform = M, easing = CUBIC_EASING)
|
|
sleep(1)
|
|
animate(I, alpha = 0, transform = matrix(), time = 1)
|