/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 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 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. Should only be changed through setMovetype() 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 /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) if(light_system == MOVABLE_LIGHT) AddComponent(/datum/component/overlay_lighting) /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) 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", "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(NAMEOF(src, anchored)) set_anchored(var_value) return TRUE if(NAMEOF(src, x)) var/turf/T = locate(var_value, y, z) if(T) forceMove(T) return TRUE return FALSE if(NAMEOF(src, y)) var/turf/T = locate(x, var_value, z) if(T) forceMove(T) return TRUE return FALSE if(NAMEOF(src, z)) var/turf/T = locate(x, y, var_value) if(T) forceMove(T) return TRUE return FALSE if(NAMEOF(src, 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) 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("[src] grabs [M] passively.", \ "[src] grabs you passively.") 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) 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(!.) 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() 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) ///Proc to modify the movement_type and hook behavior associated with it changing. /atom/movable/proc/setMovetype(newval) if(movement_type == newval) return . = movement_type 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][/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) 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() 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("[src] forcefully pushes against [AM]!", "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("[src] crushes past [AM]!", "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) . = ..() 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, CONSIG_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, COMISG_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) animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING) /atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) var/image/I if(visual_effect_icon) I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) else if(used_item) I = image(icon = used_item, loc = A, layer = A.layer + 0.1) I.plane = GAME_PLANE // Scale the icon. I.transform *= 0.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() . = ..() . += "" . += "" /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 * 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) /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)