mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* 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
1389 lines
51 KiB
Plaintext
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
|