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