Files
Yogstation/code/game/atoms_movable.dm
Byemoh d93428fe4b [MDB IGNORE] [PRE-ALPHA] MiniStation (#22568)
* Create MiniStation new.dmm

* ministation

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* ministayion

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* actual changes oops

* Update MiniStation.dmm

* Update MiniStation.dmm

* miniaturestation

* Update maps.txt

* more fixes

* fixes broken path

* more fixes

* Update MiniStation.dmm

* Update MiniStation.dmm

* filters

* robotics changes

* fixes pipes

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* spawns + job overrides

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* more work

* Update MiniStation.dmm

* misc stuff

* maint stuff

* uopdates

* fixes bad area and chair

* tool storage + fixes

* mining APC

* cargo + hooks up some disposals

* more disposals + launcher

* lights

* more fixes

* Update MiniStation.dmm

* Update MiniStation.dmm

* more fixes

* sink

* remove sign

* expand janitor room

* Update MiniStation.dmm

* ai stuff + more firealarms

* meeting room, etc

* fixes wire

* Update MiniStation.dmm

* fixes

* Update MiniStation.dmm

* yep!

* atmos area

* fixes some map errors

* bookbinder

* robo shutters

* stair and chem fix

* fixes bad decals

* honesly no clue what's different

* disposals + pets

* Update MiniStation.dmm

* asteroid area

* more fixes

* medbay mixup

* choom button

* wire toxin room to grid

* remove this

* more fixes

* Update ai_whale.dmm

* more cameras

* Update networking_machines.dm

* update freezer

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* new service

* hawk + tuah + backrooms

* special charters

* remove that lol

* move him a little

* Update MiniStation.dmm

* yeah

* Update MiniStation.dmm

* psych office + more fixes + robo stuff

* thermomachines

* Update MiniStation.dmm

* maints + APCs

* all this stuff

* cameras

* air alarms

* RD office and the like

* Update MiniStation.dmm

* fixes these

* glass floors

* Update MiniStation.dmm

* more firelocks

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* extra pipe moment

* Update MiniStation.dmm

* looking up and down/fixes

* neat

* wire

* Update MiniStation.dmm

* fixes some issues

* asdad

* better SM stuff

* amon gus

* fixing falling

* Update MiniStation.dmm

* Update MiniStation.dmm

* fixes this

* fixes

* Update MiniStation.dmm

* remove stairs

* Update MiniStation.dmm

* Update MiniStation.dmm

* Update MiniStation.dmm

* fixes some ai stuff

* Update MiniStation.dmm

* adds some missing bells

* maint

* more maint stuff

* ai upload and goodbye meeting room

* fixes multiz pda stuff

* fixes locker

* ministation fixes

* requested changes

* shadows

* removes shadows

* adds medhuds

* no smoking

* move up/down

* correct medhuds

* brig stuff

* cameras

* fixes

* more lights

* more fixes

* fix one turf

* idk randomstuff

* more lights

* navigation nation (thanks plant)

* fixes + better flooring

* jamie changes

* myriad changes

* some more stuff

* server room fix
2024-10-19 00:13:31 +01:00

1389 lines
51 KiB
Plaintext

/atom/movable
layer = OBJ_LAYER
glide_size = 8
appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
var/last_move = null
var/last_move_time = 0
var/anchored = FALSE
var/move_resist = MOVE_RESIST_DEFAULT
var/move_force = MOVE_FORCE_DEFAULT
var/pull_force = PULL_FORCE_DEFAULT
var/datum/thrownthing/throwing = null
var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported
var/throw_range = 7
var/mob/pulledby = null
var/initial_language_holder = /datum/language_holder
var/datum/language_holder/language_holder
var/verb_say = "says"
var/verb_ask = "asks"
var/verb_exclaim = "exclaims"
var/verb_whisper = "whispers"
var/verb_sing = "sings"
var/verb_yell = "yells"
var/speech_span
var/inertia_dir = 0
var/atom/inertia_last_loc
var/inertia_moving = 0
var/inertia_next_move = 0
var/inertia_move_delay = 5
var/pass_flags = NONE
/// If false makes CanPass call CanPassThrough on this type instead of using default behaviour
var/generic_canpass = TRUE
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
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
// The strongest grab we can acomplish
var/max_grab = GRAB_KILL
var/throwforce = 0
var/datum/component/orbiter/orbiting
var/can_be_z_moved = TRUE
var/zfalling = FALSE
///is the mob currently ascending or descending through z levels?
var/currently_z_moving
/// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
var/blocks_emissive = EMISSIVE_BLOCK_NONE
///Internal holder for emissive blocker object, do not use directly use blocks_emissive
var/atom/movable/render_step/emissive_blocker/em_block
///Used for the calculate_adjacencies proc for icon smoothing.
var/can_be_unanchored = FALSE
/// The degree of thermal insulation that mobs in list/contents have from the external environment, between 0 and 1
var/contents_thermal_insulation = 0
/// The degree of pressure protection that mobs in list/contents have from the external environment, between 0 and 1
var/contents_pressure_protection = 0
///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
/mutable_appearance/emissive_blocker
/mutable_appearance/emissive_blocker/New()
. = ..()
// Need to do this here because it's overriden by the parent call
color = EM_BLOCK_COLOR
appearance_flags = EMISSIVE_APPEARANCE_FLAGS
/atom/movable/Initialize(mapload, ...)
. = ..()
// #ifdef UNIT_TESTS
// if(explosion_block && !HAS_TRAIT(src, TRAIT_BLOCKING_EXPLOSIVES))
// stack_trace("[type] blocks explosives, but does not have the managing element applied")
// #endif
#if EMISSIVE_BLOCK_GENERIC != 0
#error EMISSIVE_BLOCK_GENERIC is expected to be 0 to faciliate a weird optimization hack where we rely on it being the most common.
#error Read the comment in code/game/atoms_movable.dm for details.
#endif
// This one is incredible.
// `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
// From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
// So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
// If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
// This saves several hundred milliseconds of init time.
if (blocks_emissive)
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
render_target = ref(src)
em_block = new(null, src)
overlays += em_block
if(managed_overlays)
if(islist(managed_overlays))
managed_overlays += em_block
else
managed_overlays = list(managed_overlays, em_block)
else
managed_overlays = em_block
else
var/static/mutable_appearance/emissive_blocker/blocker = new()
blocker.icon = icon
blocker.icon_state = icon_state
blocker.dir = dir
blocker.appearance_flags |= appearance_flags
blocker.plane = GET_NEW_PLANE(EMISSIVE_PLANE, PLANE_TO_OFFSET(plane))
// Ok so this is really cursed, but I want to set with this blocker cheaply while
// Still allowing it to be removed from the overlays list later
// So I'm gonna flatten it, then insert the flattened overlay into overlays AND the managed overlays list, directly
// I'm sorry
var/mutable_appearance/flat = blocker.appearance
overlays += flat
if(managed_overlays)
if(islist(managed_overlays))
managed_overlays += flat
else
managed_overlays = list(managed_overlays, flat)
else
managed_overlays = flat
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)
if(MOVABLE_LIGHT_BEAM)
AddComponent(/datum/component/overlay_lighting, is_directional = TRUE, is_beam = 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(((can_atmos_pass == ATMOS_PASS_DENSITY && density) || can_atmos_pass == ATMOS_PASS_NO) && isturf(loc))
can_atmos_pass = ATMOS_PASS_YES
air_update_turf()
loc.handle_atom_del(src)
if(opacity)
RemoveElement(/datum/element/light_blocking)
invisibility = INVISIBILITY_ABSTRACT
if(pulledby)
pulledby.stop_pulling()
if(pulling)
stop_pulling()
if(orbiting)
orbiting.end_orbit(src)
orbiting = null
LAZYCLEARLIST(client_mobs_in_contents)
. = ..()
for(var/movable_content in contents)
qdel(movable_content)
moveToNullspace()
vis_locs = null //clears this atom out of all viscontents
// Checking length(vis_contents) before cutting has significant speed benefits
if (length(vis_contents))
vis_contents.Cut()
/atom/movable/proc/update_emissive_block()
// This one is incredible.
// `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
// From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
// So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
// If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
// This saves several hundred milliseconds of init time.
if (blocks_emissive)
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
if(em_block)
SET_PLANE(em_block, EMISSIVE_PLANE, src)
else if(!QDELETED(src))
render_target = ref(src)
em_block = new(null, src)
return em_block
// Implied else if (blocks_emissive == EMISSIVE_BLOCK_NONE) -> return
// EMISSIVE_BLOCK_GENERIC == 0
else
return fast_emissive_blocker(src)
/// Generates a space underlay for a turf
/// This provides proper lighting support alongside just looking nice
/// Accepts the appearance to make "spaceish", and the turf we're doing this for
/proc/generate_space_underlay(mutable_appearance/underlay_appearance, turf/generate_for)
underlay_appearance.icon = 'icons/turf/space.dmi'
underlay_appearance.icon_state = "0"
SET_PLANE(underlay_appearance, PLANE_SPACE, generate_for)
if(!generate_for.render_target)
generate_for.render_target = ref(generate_for)
var/atom/movable/render_step/emissive_blocker/em_block = new(null, generate_for)
underlay_appearance.overlays += em_block
// We used it because it's convienient and easy, but it's gotta go now or it'll hang refs
QDEL_NULL(em_block)
// We're gonna build a light, and mask it with the base turf's appearance
// grab a 32x32 square of it
// I would like to use GLOB.starbright_overlays here
// But that breaks down for... some? reason. I think recieving a render relay breaks keep_together or something
// So we're just gonna accept that this'll break with starlight color changing. hardly matters since this is really only for offset stuff, but I'd love to fix it someday
var/mutable_appearance/light = new(GLOB.starlight_objects[GET_TURF_PLANE_OFFSET(generate_for) + 1])
light.render_target = ""
light.appearance_flags |= KEEP_TOGETHER
// Now apply a copy of the turf, set to multiply
// This will multiply against our light, so we only light up the bits that aren't "on" the wall
var/mutable_appearance/mask = new(generate_for.appearance)
mask.blend_mode = BLEND_MULTIPLY
mask.render_target = ""
mask.pixel_x = 0
mask.pixel_y = 0
mask.pixel_w = 0
mask.pixel_z = 0
mask.transform = null
mask.underlays = list() // Begone foul lighting overlay
SET_PLANE(mask, FLOAT_PLANE, generate_for)
mask.layer = FLOAT_LAYER
// Bump the opacity to full, will this work?
mask.color = list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,255, 0,0,0,0)
light.overlays += mask
underlay_appearance.overlays += light
// Now, we're going to make a copy of the mask. Instead of using it to multiply against our light
// We're going to use it to multiply against the turf lighting plane. Going to mask away the turf light
// And rely on LIGHTING_MASK_LAYER to ensure we mask ONLY that bit
var/mutable_appearance/turf_mask = new(mask.appearance)
SET_PLANE(turf_mask, LIGHTING_PLANE, generate_for)
turf_mask.layer = LIGHTING_MASK_LAYER
/// Any color becomes white. Anything else is black, and it's fully opaque
/// Ought to work
turf_mask.color = list(255,255,255,0, 255,255,255,0, 255,255,255,0, 0,0,0,0, 0,0,0,255)
underlay_appearance.overlays += turf_mask
/atom/movable/update_overlays()
var/list/overlays = ..()
var/emissive_block = update_emissive_block()
if(emissive_block)
// Emissive block should always go at the beginning of the list
overlays.Insert(1, emissive_block)
return overlays
/atom/movable/proc/onZImpact(turf/impacted_turf, levels, impact_flags = NONE)
SHOULD_CALL_PARENT(TRUE)
if(!(impact_flags & ZIMPACT_NO_MESSAGE))
visible_message(
span_danger("[src] crashes into [impacted_turf]!"),
span_userdanger("You crash into [impacted_turf]!"),
)
if(!(impact_flags & ZIMPACT_NO_SPIN))
INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2)
SEND_SIGNAL(src, COMSIG_ATOM_ON_Z_IMPACT, impacted_turf, levels)
return TRUE
/*
* Attempts to move using zMove if direction is UP or DOWN, step if not
*
* Args:
* direction: The direction to go
* z_move_flags: bitflags used for checks in zMove and can_z_move
*/
/atom/movable/proc/try_step_multiz(direction, z_move_flags = ZMOVE_FLIGHT_FLAGS)
if(direction == UP || direction == DOWN)
return zMove(direction, null, z_move_flags)
return step(src, direction)
/*
* The core multi-z movement proc. Used to move a movable through z levels.
* If target is null, it'll be determined by the can_z_move proc, which can potentially return null if
* conditions aren't met (see z_move_flags defines in __DEFINES/movement.dm for info) or if dir isn't set.
* Bear in mind you don't need to set both target and dir when calling this proc, but at least one or two.
* This will set the currently_z_moving to CURRENTLY_Z_MOVING_GENERIC if unset, and then clear it after
* Forcemove().
*
*
* Args:
* * dir: the direction to go, UP or DOWN, only relevant if target is null.
* * target: The target turf to move the src to. Set by can_z_move() if null.
* * z_move_flags: bitflags used for various checks in both this proc and can_z_move(). See __DEFINES/movement.dm.
*/
/atom/movable/proc/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS)
if(!target)
target = can_z_move(dir, get_turf(src), null, z_move_flags)
if(!target)
set_currently_z_moving(FALSE, TRUE)
return FALSE
var/list/moving_movs = get_z_move_affected(z_move_flags)
for(var/atom/movable/movable as anything in moving_movs)
movable.currently_z_moving = currently_z_moving || CURRENTLY_Z_MOVING_GENERIC
movable.forceMove(target)
movable.set_currently_z_moving(FALSE, TRUE)
// This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range.
if(z_move_flags & ZMOVE_CHECK_PULLS)
for(var/atom/movable/moved_mov as anything in moving_movs)
if(z_move_flags & ZMOVE_CHECK_PULLEDBY && moved_mov.pulledby && (moved_mov.z != moved_mov.pulledby.z || get_dist(moved_mov, moved_mov.pulledby) > 1))
moved_mov.pulledby.stop_pulling()
if(z_move_flags & ZMOVE_CHECK_PULLING)
moved_mov.check_pulling(TRUE)
return TRUE
/// Returns a list of movables that should also be affected when src moves through zlevels, and src.
/atom/movable/proc/get_z_move_affected(z_move_flags)
. = list(src)
if(buckled_mobs)
. |= buckled_mobs
if(!(z_move_flags & ZMOVE_INCLUDE_PULLED))
return
for(var/mob/living/buckled as anything in buckled_mobs)
if(buckled.pulling)
. |= buckled.pulling
if(pulling)
. |= pulling
/**
* Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so.
* Args:
* * direction: the direction to go, UP or DOWN, only relevant if target is null.
* * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source.
* * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm.
* * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels.
*/
/atom/movable/proc/can_z_move(direction, turf/start, turf/destination, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider)
if(!start)
start = get_turf(src)
if(!start)
return FALSE
if(!direction)
if(!destination)
return FALSE
direction = get_dir_multiz(start, destination)
if(direction != UP && direction != DOWN)
return FALSE
if(!destination)
destination = get_step_multiz(start, direction)
if(!destination)
if(z_move_flags & ZMOVE_FEEDBACK)
to_chat(rider || src, span_warning("There's nowhere to go in that direction!"))
return FALSE
if(z_move_flags & ZMOVE_FALL_CHECKS && (throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start)))
return FALSE
if(z_move_flags & ZMOVE_CAN_FLY_CHECKS && !(movement_type & (FLYING|FLOATING)) && has_gravity(start))
if(z_move_flags & ZMOVE_FEEDBACK)
if(rider)
to_chat(rider, span_warning("[src] is is not capable of flight."))
else
to_chat(src, span_warning("You are not Superman."))
return FALSE
if(pass_flags & PASSFLOOR) //ghosts bypass turf z checks
return destination
if((!(z_move_flags & ZMOVE_IGNORE_OBSTACLES) && !(start.zPassOut(direction) && destination.zPassIn(direction))) || (!(z_move_flags & ZMOVE_ALLOW_ANCHORED) && anchored))
if(z_move_flags & ZMOVE_FEEDBACK)
to_chat(rider || src, span_warning("You couldn't move there!"))
return FALSE
return destination //used by some child types checks and zMove()
/// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others.
/atom/movable/proc/set_currently_z_moving(new_z_moving_value, forced = FALSE)
if(forced)
currently_z_moving = new_z_moving_value
return TRUE
var/old_z_moving_value = currently_z_moving
currently_z_moving = max(currently_z_moving, new_z_moving_value)
return currently_z_moving > old_z_moving_value
/atom/movable/vv_edit_var(var_name, var_value)
var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds")
var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height")
if(var_name in banned_edits)
return FALSE //PLEASE no.
if((var_name in careful_edits) && (var_value % world.icon_size) != 0)
return FALSE
switch(var_name)
if("x")
var/turf/T = locate(var_value, y, z)
if(T)
forceMove(T)
return TRUE
return FALSE
if("y")
var/turf/T = locate(x, var_value, z)
if(T)
forceMove(T)
return TRUE
return FALSE
if("z")
var/turf/T = locate(x, y, var_value)
if(T)
forceMove(T)
return TRUE
return FALSE
if("loc")
if(istype(var_value, /atom))
forceMove(var_value)
return TRUE
else if(isnull(var_value))
moveToNullspace()
return TRUE
return FALSE
return ..()
/atom/movable/proc/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE)
if(QDELETED(pulled_atom))
return FALSE
if(!(pulled_atom.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(pulled_atom == pulling)
setGrabState(state)
if(isliving(pulled_atom))
var/mob/living/pulled_mob = pulled_atom
pulled_mob.grabbedby(src)
return TRUE
stop_pulling()
if(pulled_atom.pulledby)
log_combat(pulled_atom, pulled_atom.pulledby, "pulled from", src)
pulled_atom.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = pulled_atom
pulled_atom.set_pulledby(src)
SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, pulled_atom, state, force)
setGrabState(state)
if(ismob(pulled_atom))
var/mob/pulled_mob = pulled_atom
log_combat(src, pulled_mob, "grabbed", addition="passive grab")
if(!supress_message)
pulled_mob.visible_message(span_warning("[src] grabs [pulled_mob] passively."), \
span_danger("[src] grabs you passively."))
return TRUE
/atom/movable/proc/stop_pulling()
if(!pulling)
return
pulling.set_pulledby(null)
setGrabState(GRAB_PASSIVE)
var/atom/movable/old_pulling = pulling
pulling = null
SEND_SIGNAL(old_pulling, COMSIG_ATOM_NO_LONGER_PULLED, src)
SEND_SIGNAL(src, COMSIG_ATOM_NO_LONGER_PULLING, old_pulling)
///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/moving_atom)
if(!pulling)
return FALSE
if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling))
stop_pulling()
return FALSE
if(isliving(pulling))
var/mob/living/pulling_mob = pulling
if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
stop_pulling()
return FALSE
if(moving_atom == loc && pulling.density)
return FALSE
var/move_dir = get_dir(pulling.loc, moving_atom)
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/moving_atom)
. = ..()
if(!. || !isliving(moving_atom))
return
var/mob/living/pulled_mob = moving_atom
set_pull_offsets(pulled_mob, grab_state)
/**
* Checks if the pulling and pulledby should be stopped because they're out of reach.
* If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs.
*/
/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE)
if(pulling)
if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed))
stop_pulling()
else if(!isturf(loc))
stop_pulling()
else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.")
stop_pulling()
else if(pulling.anchored || pulling.move_resist > move_force)
stop_pulling()
if(!only_pulling && pulledby && moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, pulledby) > 1 || z != pulledby.z)) //separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
/atom/movable/proc/set_glide_size(target = 8)
SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
glide_size = target
for(var/atom/movable/AM in buckled_mobs)
AM.set_glide_size(target)
/**
* meant for movement with zero side effects. only use for objects that are supposed to move "invisibly" (like camera mobs or ghosts)
* if you want something to move onto a tile with a beartrap or recycler or tripmine or mouse without that object knowing about it at all, use this
* most of the time you want forceMove()
*/
/atom/movable/proc/abstract_move(atom/new_loc)
var/atom/old_loc = loc
var/direction = get_dir(old_loc, new_loc)
loc = new_loc
Moved(old_loc, direction, TRUE, momentum_change = FALSE)
////////////////////////////////////////
// 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, direction, glide_size_override = 0, update_dir = TRUE)
. = FALSE
if(!newloc || newloc == loc)
return
if(!direction)
direction = get_dir(src, newloc)
if(set_dir_on_move && dir != direction && update_dir)
setDir(direction)
var/is_multi_tile_object = is_multi_tile_object(src)
var/list/old_locs
if(is_multi_tile_object && isturf(loc))
old_locs = locs // locs is a special list, this is effectively the same as .Copy() but with less steps
for(var/atom/exiting_loc as anything in old_locs)
if(!exiting_loc.Exit(src, direction))
return
else
if(!loc.Exit(src, direction))
return
var/list/new_locs
if(is_multi_tile_object && isturf(newloc))
new_locs = block(
newloc,
locate(
min(world.maxx, newloc.x + CEILING(bound_width / 32, 1)),
min(world.maxy, newloc.y + CEILING(bound_height / 32, 1)),
newloc.z
)
) // If this is a multi-tile object then we need to predict the new locs and check if they allow our entrance.
for(var/atom/entering_loc as anything in new_locs)
SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_ENTER, entering_loc)
if(!entering_loc.Enter(src))
return
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, entering_loc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
return
else // Else just try to enter the single destination.
SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_ENTER, newloc)
if(!newloc.Enter(src))
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
if(old_locs) // This condition will only be true if it is a multi-tile object.
for(var/atom/exited_loc as anything in (old_locs - new_locs))
exited_loc.Exited(src, direction)
else // Else there's just one loc to be exited.
oldloc.Exited(src, direction)
if(oldarea != newarea)
oldarea.Exited(src, direction)
if(new_locs) // Same here, only if multi-tile.
for(var/atom/entered_loc as anything in (new_locs - old_locs))
entered_loc.Entered(src, oldloc, old_locs)
else
newloc.Entered(src, oldloc, old_locs)
if(oldarea != newarea)
newarea.Entered(src, oldarea)
Moved(oldloc, direction, FALSE, old_locs)
////////////////////////////////////////
/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
var/atom/movable/pullee = pulling
var/turf/current_turf = loc
if(!moving_from_pull)
check_pulling(z_allowed = TRUE)
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
//Early override for some cases like diagonal movement
if(glide_size_override && glide_size != 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 && update_dir)
setDir(first_step_dir)
else if(!inertia_moving)
newtonian_move(direct)
if(client_mobs_in_contents)
update_parallax_contents()
moving_diagonally = 0
return
if(!loc || (loc == oldloc && oldloc != newloc))
last_move = 0
set_currently_z_moving(FALSE, TRUE)
return
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
//puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus.
if(!pulling.pulling?.moving_from_pull)
var/pull_dir = get_dir(pulling, src)
var/target_turf = current_turf
// Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls.
// You may wonder why we're not just forcemoving the pulling movable and regrabbing it.
// The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines.
if(pulling.z != z)
target_turf = get_step(pulling, get_dir(pulling, current_turf))
if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1)
pulling.move_from_pull(src, target_turf, glide_size)
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 && dir != direct && update_dir)
setDir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
. = FALSE
if(currently_z_moving)
if(. && loc == newloc)
var/turf/pitfall = get_turf(src)
pitfall.zFall(src, falling_from_move = TRUE)
else
set_currently_z_moving(FALSE, TRUE)
/// Called when src is being moved to a target turf because another movable (puller) is moving around.
/atom/movable/proc/move_from_pull(atom/movable/puller, turf/target_turf, glide_size_override)
moving_from_pull = puller
Move(target_turf, get_dir(src, target_turf), glide_size_override)
moving_from_pull = null
/**
* Called after a successful Move(). By this point, we've already moved.
* Arguments:
* * old_loc is the location prior to the move. Can be null to indicate nullspace.
* * movement_dir is the direction the movement took place. Can be NONE if it was some sort of teleport.
* * The forced flag indicates whether this was a forced move, which skips many checks of regular movement.
* * The old_locs is an optional argument, in case the moved movable was present in multiple locations before the movement.
* * momentum_change represents whether this movement is due to a "new" force if TRUE or an already "existing" force if FALSE
**/
/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
SHOULD_CALL_PARENT(TRUE)
if (!inertia_moving && momentum_change)
newtonian_move(movement_dir)
// If we ain't moving diagonally right now, update our parallax
// We don't do this all the time because diag movements should trigger one call to this, not two
// Waste of cpu time, and it fucks the animate
if (!moving_diagonally && client_mobs_in_contents)
update_parallax_contents()
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, movement_dir, forced, old_locs, momentum_change)
if(old_loc)
SEND_SIGNAL(old_loc, COMSIG_ATOM_ABSTRACT_EXITED, src, movement_dir)
if(loc)
SEND_SIGNAL(loc, COMSIG_ATOM_ABSTRACT_ENTERED, src, old_loc, old_locs)
var/turf/old_turf = get_turf(old_loc)
var/turf/new_turf = get_turf(src)
if (old_turf?.z != new_turf?.z)
var/same_z_layer = (GET_TURF_PLANE_OFFSET(old_turf) == GET_TURF_PLANE_OFFSET(new_turf))
on_changed_z_level(old_turf, new_turf, same_z_layer)
SSdemo.mark_dirty(src)
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/bumped_atom)
if(!bumped_atom)
CRASH("Bump was called with no argument.")
SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
. = ..()
if(!QDELETED(throwing))
throwing.hit_atom(bumped_atom)
. = TRUE
if(QDELETED(bumped_atom))
return
bumped_atom.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
if(destination)
var/turf/old_turf = get_turf(src)
var/turf/new_turf = get_turf(destination)
if(new_turf && ismob(src))
var/mob/M = src
if(is_secret_level(new_turf.z) && !M.client?.holder && old_turf.z != new_turf.z)
return
. = doMove(destination)
else
CRASH("No valid destination passed into forceMove")
/atom/movable/proc/moveToNullspace()
return doMove(null)
/atom/movable/proc/doMove(atom/destination)
. = FALSE
if(destination)
///zMove already handles whether a pull from another movable should be broken.
if(pulledby && !currently_z_moving)
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)
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
/**
* Called when a movable changes z-levels.
*
* Arguments:
* * old_turf - The previous turf they were on before.
* * new_turf - The turf they have now entered.
* * same_z_layer - If their old and new z levels are on the same level of plane offsets or not
* * notify_contents - Whether or not to notify the movable's contents that their z-level has changed. NOTE, IF YOU SET THIS, YOU NEED TO MANUALLY SET PLANE OF THE CONTENTS LATER
*/
/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf, same_z_layer)
// If our turfs are on different z "layers", recalc our planes
if(!same_z_layer && !QDELETED(src))
SET_PLANE(src, PLANE_TO_TRUE(src.plane), new_turf)
// a TON of overlays use planes, and thus require offsets
// so we do this. sucks to suck
update_appearance()
if(update_on_z)
// I so much wish this could be somewhere else. alas, no.
for(var/image/update as anything in update_on_z)
SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
if(update_overlays_on_z)
// This EVEN more so
cut_overlay(update_overlays_on_z)
// This even more so
for(var/mutable_appearance/update in update_overlays_on_z)
SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
add_overlay(update_overlays_on_z)
if(!notify_contents)
return
for (var/atom/movable/content as anything in src) // Notify contents of Z-transition.
content.on_changed_z_level(old_turf, new_turf, same_z_layer)
/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 TRUE
if(pulledby && (pulledby.pulledby != src || moving_from_pull))
return TRUE
if(throwing)
return TRUE
if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir) & COMSIG_MOVABLE_ALLOW_SPACEMOVE)
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
/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity
if(!loc || Process_Spacemove(0))
inertia_dir = 0
return 0
inertia_dir = direction
if(!direction)
return 1
inertia_last_loc = loc
SSspacedrift.processing[src] = src
return 1
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
set waitfor = 0
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
return hit_atom.hitby(src, throwingdatum=throwingdatum)
/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum)
if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO))))
step(src, AM.dir)
..()
/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, quickstart = TRUE)
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
return
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force)
/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, quickstart = TRUE) //If this returns FALSE then callback will not be called.
. = FALSE
if (!target || speed <= 0)
return
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW)
return
if (pulledby)
pulledby.stop_pulling()
//They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
var/user_momentum = thrower.movement_delay()
if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
user_momentum = world.tick_lag
user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
if (get_dir(thrower, target) & last_move)
user_momentum = user_momentum //basically a noop, but needed
else if (get_dir(target, thrower) & last_move)
user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
else
user_momentum = 0
if (user_momentum)
//first lets add that momentum to range.
range *= (user_momentum / speed) + 1
//then lets add it to speed
speed += user_momentum
if (speed <= 0)
return//no throw speed, the user was moving too fast.
. = TRUE // No failure conditions past this point.
if(throwing?.callback) // Already being thrown, make sure to invoke any callbacks
throwing.callback.Invoke()
var/datum/thrownthing/TT = new()
TT.thrownthing = src
TT.target = target
TT.target_turf = get_turf(target)
TT.init_dir = get_dir(src, target)
TT.maxrange = range
TT.speed = speed
TT.thrower = thrower
TT.diagonals_first = diagonals_first
TT.force = force
TT.callback = callback
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
var/dx = (target.x > src.x) ? EAST : WEST
var/dy = (target.y > src.y) ? NORTH : SOUTH
if (dist_x == dist_y)
TT.pure_diagonal = 1
else if(dist_x <= dist_y)
var/olddist_x = dist_x
var/olddx = dx
dist_x = dist_y
dist_y = olddist_x
dx = dy
dy = olddx
TT.dist_x = dist_x
TT.dist_y = dist_y
TT.dx = dx
TT.dy = dy
TT.diagonal_error = dist_x/2 - dist_y
TT.start_time = world.time
if(pulledby)
pulledby.stop_pulling()
throwing = TT
if(spin)
SpinAnimation(5, 1)
SEND_SIGNAL(src, COMSIG_MOVABLE_POST_THROW, TT, spin)
SSthrowing.processing[src] = TT
if (SSthrowing.state == SS_PAUSED && length(SSthrowing.currentrun))
SSthrowing.currentrun[src] = TT
if (quickstart)
TT.tick()
/atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
if(!buckled_mob.Move(newloc, direct, glide_size_override))
forceMove(buckled_mob.loc)
last_move = buckled_mob.last_move
inertia_dir = last_move
buckled_mob.inertia_dir = last_move
return FALSE
return TRUE
/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE)
. = AM.force_pushed(src, force, direction)
if(!silent && .)
visible_message(span_warning("[src] forcefully pushes against [AM]!"), span_warning("You forcefully push against [AM]!"))
/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE)
. = AM.move_crushed(src, force, direction)
if(!silent && .)
visible_message(span_danger("[src] crushes past [AM]!"), span_danger("You crush [AM]!"))
/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
/atom/movable/CanAllowThrough(atom/movable/mover, turf/target)
SHOULD_CALL_PARENT(TRUE)
. = ..()
if(mover in buckled_mobs)
return TRUE
/// Returns true or false to allow src to move through the blocker, mover has final say
/atom/movable/proc/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
return blocker_opinion
// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S)
return
// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S)
return
/atom/movable/proc/get_spacemove_backup()
var/atom/movable/dense_object_backup
for(var/A in orange(1, get_turf(src)))
if(isarea(A))
continue
else if(isturf(A))
var/turf/turf = A
if(!turf.density)
continue
return turf
else
var/atom/movable/AM = A
if(!AM.CanPass(src) || AM.density)
if(AM.anchored)
return AM
dense_object_backup = AM
break
. = dense_object_backup
//called when a mob resists while inside a container that is itself inside something.
/atom/movable/proc/relay_container_resist(mob/living/user, obj/O)
return
/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && (visual_effect_icon || used_item))
do_item_attack_animation(A, visual_effect_icon, used_item)
if(A == src)
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = 8
else if(direction & SOUTH)
pixel_y_diff = -8
if(direction & EAST)
pixel_x_diff = 8
else if(direction & WEST)
pixel_x_diff = -8
animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 0.2 SECONDS)
animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, time = 0.2 SECONDS)
/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>"
VV_DROPDOWN_OPTION(VV_HK_EDIT_PARTICLES, "Edit Particles")
VV_DROPDOWN_OPTION(VV_HK_ADD_EMITTER, "Add Emitter")
VV_DROPDOWN_OPTION(VV_HK_REMOVE_EMITTER, "Remove Emitter")
/atom/movable/vv_do_topic(list/href_list)
. = ..()
if(href_list[VV_HK_EDIT_PARTICLES])
if(!check_rights(R_VAREDIT))
return
var/client/interacted_client = usr.client
interacted_client?.open_particle_editor(src)
if(href_list[VV_HK_ADD_EMITTER])
if(!check_rights(R_VAREDIT))
return
var/key = stripped_input(usr, "Enter a key for your emitter", "Emitter Key")
var/lifetime = input("How long should this live for in deciseconds? 0 for infinite, -1 for a single burst.", "Lifespan") as null|num
if(!key)
return
switch(alert("Should this be a pre-filled emitter (empty emitters don't support timers)?",,"Yes","No","Cancel"))
if("Yes")
var/choice = input(usr, "Choose an emitter to add", "Choose an Emitter") as null|anything in subtypesof(/obj/emitter)
var/should_burst = FALSE
if(lifetime == -1)
should_burst = TRUE
if(choice)
add_emitter(choice, key, lifespan = lifetime, burst_mode = should_burst)
if("No")
add_emitter(/obj/emitter, key)
else
return
if(href_list[VV_HK_REMOVE_EMITTER])
if(!check_rights(R_VAREDIT))
return
if(!master_holder?.emitters.len)
return
var/removee = input(usr, "Choose an emitter to remove", "Choose an Emitter") as null|anything in master_holder?.emitters
if(!removee)
return
remove_emitter(removee)
/atom/movable/proc/ex_check(ex_id)
if(!ex_id)
return TRUE
LAZYINITLIST(acted_explosions)
if(ex_id in acted_explosions)
return FALSE
acted_explosions += ex_id
return TRUE
//TODO: Better floating
/atom/movable/proc/float(on)
if(throwing)
return
if(on && !(movement_type & FLOATING))
animate(src, pixel_y = pixel_y + 2, time = 1 SECONDS, loop = -1)
animate(pixel_y = pixel_y - 2, time = 1 SECONDS)
setMovetype(movement_type | FLOATING)
else if (!on && (movement_type & FLOATING))
animate(src, pixel_y = initial(pixel_y), time = 1 SECONDS)
setMovetype(movement_type & ~FLOATING)
/* Language procs
* Unless you are doing something very specific, these are the ones you want to use.
*/
/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one.
/atom/movable/proc/get_language_holder(get_minds = TRUE)
if(!language_holder)
language_holder = new initial_language_holder(src)
return language_holder
/// Grants the supplied language and sets omnitongue true.
/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.grant_language(language, understood, spoken, source)
/// Grants every language.
/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
var/datum/language_holder/LH = get_language_holder()
return LH.grant_all_languages(understood, spoken, grant_omnitongue, source)
/// Removes a single language.
/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_language(language, understood, spoken, source)
/// Removes every language and sets omnitongue false.
/atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_all_languages(source, remove_omnitongue)
/// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later.
/atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.add_blocked_language(language, source)
/// Removes a language from the blocked language list.
/atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM)
var/datum/language_holder/LH = get_language_holder()
return LH.remove_blocked_language(language, source)
/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language.
/atom/movable/proc/has_language(language, spoken = FALSE)
var/datum/language_holder/LH = get_language_holder()
return LH.has_language(language, spoken)
/// Checks if atom can speak the language.
/atom/movable/proc/can_speak_language(language)
var/datum/language_holder/LH = get_language_holder()
return LH.can_speak_language(language)
/// Returns the result of tongue specific limitations on spoken languages.
/atom/movable/proc/could_speak_language(language)
return TRUE
/// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible.
/atom/movable/proc/get_selected_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_selected_language()
/// Gets a random understood language, useful for hallucinations and such.
/atom/movable/proc/get_random_understood_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_random_understood_language()
/// Gets a random spoken language, useful for forced speech and such.
/atom/movable/proc/get_random_spoken_language()
var/datum/language_holder/LH = get_language_holder()
return LH.get_random_spoken_language()
/// Copies all languages into the supplied atom/language holder. Source should be overridden when you
/// do not want the language overwritten by later atom updates or want to avoid blocked languages.
/atom/movable/proc/copy_languages(from_holder, source_override)
if(isatom(from_holder))
var/atom/movable/thing = from_holder
from_holder = thing.get_language_holder()
var/datum/language_holder/LH = get_language_holder()
return LH.copy_languages(from_holder, source_override)
/// Empties out the atom specific languages and updates them according to the current atoms language holder.
/// As a side effect, it also creates missing language holders in the process.
/atom/movable/proc/update_atom_languages()
var/datum/language_holder/LH = get_language_holder()
return LH.update_atom_languages(src)
/* End language procs */
//Returns an atom's power cell, if it has one. Overload for individual items.
/atom/movable/proc/get_cell()
return
/atom/movable/proc/can_be_pulled(user, grab_state, force)
if(src == user || !isturf(loc))
return FALSE
if(anchored || throwing)
return FALSE
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
return FALSE
return TRUE
/// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment.
/mob/living/proc/on_fall()
return
/**
* 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)
if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher.
REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
if(GRAB_AGGRESSIVE)
if(. >= GRAB_NECK) // Grab got downgraded.
REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
else // Grab got upgraded from a passive one.
ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
if(GRAB_NECK, GRAB_KILL)
if(. <= GRAB_AGGRESSIVE)
ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
// Simple helper to face what you clicked on, in case it should be needed in more than one place
/atom/movable/proc/face_atom(atom/A, forced = FALSE)
if(!A || !x || !y || !A.x || !A.y )
return
var/dx = A.x - x
var/dy = A.y - y
if(!dx && !dy) // Wall items are graphically shifted but on the floor
if(A.pixel_y > 16)
setDir(NORTH, forced)
else if(A.pixel_y < -16)
setDir(SOUTH, forced)
else if(A.pixel_x > 16)
setDir(EAST, forced)
else if(A.pixel_x < -16)
setDir(WEST, forced)
return
if(abs(dx) < abs(dy))
if(dy > 0)
setDir(NORTH, forced)
else
setDir(SOUTH, forced)
else
if(dx > 0)
setDir(EAST, forced)
else
setDir(WEST, forced)
/atom/movable/proc/sendToBackrooms()
var/backrooms_level = SSmapping.levels_by_trait(ZTRAIT_PROCEDURAL_MAINTS)
if(LAZYLEN(backrooms_level))
var/turf/destination = find_safe_turf(zlevels = backrooms_level, dense_atoms = FALSE)
if(destination)
forceMove(destination)
return TRUE