From 463c2fc71d0dde8a36dcf6a4c781319d7d7601b5 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Tue, 7 Dec 2021 14:27:12 +0100 Subject: [PATCH] [MIRROR] multiz movement refactor redux [MDB IGNORE] (#9925) * multiz movement refactor redux (#62132) * multiz movement refactor redux Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- .../signals_atom/signals_atom_movable.dm | 5 + code/__DEFINES/misc.dm | 5 - code/__DEFINES/movement.dm | 50 +++++ code/controllers/subsystem/throwing.dm | 5 +- code/datums/components/omen.dm | 6 +- code/datums/components/riding/riding.dm | 15 +- code/datums/components/riding/riding_mob.dm | 36 ++-- .../components/riding/riding_vehicle.dm | 23 +++ code/datums/elements/movetype_handler.dm | 3 + code/datums/holocall.dm | 2 +- code/game/atoms.dm | 4 +- code/game/atoms_movable.dm | 182 +++++++++++++----- .../machinery/computer/camera_advanced.dm | 12 +- code/game/objects/buckling.dm | 13 +- code/game/objects/items/storage/boxes.dm | 2 +- code/game/objects/structures/bonfire.dm | 2 +- .../construction_console.dm | 2 +- .../objects/structures/deployable_turret.dm | 2 +- code/game/objects/structures/ladders.dm | 18 +- code/game/objects/structures/stairs.dm | 30 ++- code/game/turfs/open/openspace.dm | 37 ++++ code/game/turfs/open/space/space.dm | 22 +-- code/game/turfs/turf.dm | 72 ++++--- code/modules/antagonists/blob/overmind.dm | 3 +- .../antagonists/disease/disease_mob.dm | 3 + code/modules/awaymissions/away_props.dm | 2 +- .../mob/dead/observer/observer_movement.dm | 13 +- code/modules/mob/living/living.dm | 35 ++-- code/modules/mob/living/living_movement.dm | 44 ++++- code/modules/mob/living/silicon/ai/ai.dm | 9 +- .../mob/living/silicon/ai/freelook/eye.dm | 24 +-- .../modules/mob/living/silicon/ai/multicam.dm | 2 +- code/modules/mob/mob_movement.dm | 53 ++--- code/modules/power/singularity/emitter.dm | 2 +- code/modules/power/supermatter/supermatter.dm | 5 +- .../research/xenobiology/xenobio_camera.dm | 10 +- code/modules/shuttle/navigation_computer.dm | 2 +- code/modules/vending/_vending.dm | 2 +- 38 files changed, 497 insertions(+), 260 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index a62ce90881b..ecbef6ca7e7 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -72,3 +72,8 @@ #define COMSIG_MOVABLE_CHANGE_DUCT_LAYER "movable_change_duct_layer" ///Called when a movable is teleported from `do_teleport()`: (destination, channel) #define COMSIG_MOVABLE_TELEPORTED "movable_teleported" + +/// from /mob/living/can_z_move, sent to whatever the mob is buckled to. Only ridable movables should be ridden up or down btw. +#define COMSIG_BUCKLED_CAN_Z_MOVE "ridden_pre_can_z_move" + #define COMPONENT_RIDDEN_STOP_Z_MOVE 1 + #define COMPONENT_RIDDEN_ALLOW_Z_MOVE 2 diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index d94f1481dc0..d1400e6f4e3 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -489,11 +489,6 @@ GLOBAL_LIST_INIT(pda_styles, sort_list(list(MONO, VT, ORBITRON, SHARE))) //Misc text define. Does 4 spaces. Used as a makeshift tabulator. #define FOURSPACES "    " -// possible bitflag return values of intercept_zImpact(atom/movable/AM, levels = 1) calls -#define FALL_INTERCEPTED (1<<0) //Stops the movable from falling further and crashing on the ground -#define FALL_NO_MESSAGE (1<<1) //Used to suppress the "[A] falls through [old_turf]" messages where it'd make little sense at all, like going downstairs. -#define FALL_STOP_INTERCEPTING (1<<2) //Used in situations where halting the whole "intercept" loop would be better, like supermatter dusting (and thus deleting) the atom. - // Play time / EXP #define PLAYTIME_HARDCORE_RANDOM 120 diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index 93d5eb98168..eb52cafdc58 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -14,3 +14,53 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) /// The whole result is then clamped to within the range above. /// Not very readable but it works #define DELAY_TO_GLIDE_SIZE(delay) (clamp(((32 / max((delay) / world.tick_lag, 1)) * GLOB.glide_size_multiplier), MIN_GLIDE_SIZE, MAX_GLIDE_SIZE)) + +/** + * currently_z_moving defines. Higher numbers mean higher priority. + * This one is for falling down open space from stuff such as deleted tile, pit grate... + */ +#define CURRENTLY_Z_FALLING 1 +/// currently_z_moving is set to this in zMove() if 0. +#define CURRENTLY_Z_MOVING_GENERIC 2 +/// This one is for falling down open space from movement. +#define CURRENTLY_Z_FALLING_FROM_MOVE 3 +/// This one is for going upstairs. +#define CURRENTLY_Z_ASCENDING 4 + +/// possible bitflag return values of [atom/proc/intercept_zImpact] calls +/// Stops the movable from falling further and crashing on the ground. Example: stairs. +#define FALL_INTERCEPTED (1<<0) +/// Suppresses the "[movable] falls through [old_turf]" message because it'd make little sense in certain contexts like climbing stairs. +#define FALL_NO_MESSAGE (1<<1) +/// Used when the whole intercept_zImpact forvar loop should be stopped. For example: when someone falls into the supermatter and becomes dust. +#define FALL_STOP_INTERCEPTING (1<<2) +/// Used when the grip on a pulled object shouldn't be broken. +#define FALL_RETAIN_PULL (1<<3) + +/// Runs check_pulling() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing. +#define ZMOVE_CHECK_PULLING (1<<0) +/// Checks if pulledby is nearby. if not, stop being pulled. +#define ZMOVE_CHECK_PULLEDBY (1<<1) +/// flags for different checks done in [/atom/movable/proc/can_z_move]. Should be self-explainatory. +#define ZMOVE_FALL_CHECKS (1<<2) +#define ZMOVE_CAN_FLY_CHECKS (1<<3) +#define ZMOVE_INCAPACITATED_CHECKS (1<<4) +/// Doesn't call zPassIn() and zPassOut() +#define ZMOVE_IGNORE_OBSTACLES (1<<5) +/// Gives players chat feedbacks if they're unable to move through z levels. +#define ZMOVE_FEEDBACK (1<<6) +/// Whether we check the movable (if it exists) the living mob is buckled on or not. +#define ZMOVE_ALLOW_BUCKLED (1<<7) +/// If the movable is actually ventcrawling vertically. +#define ZMOVE_VENTCRAWLING (1<<8) +/// Includes movables that're either pulled by the source or mobs buckled to it in the list of moving movables. +#define ZMOVE_INCLUDE_PULLED (1<<9) + +#define ZMOVE_CHECK_PULLS (ZMOVE_CHECK_PULLING|ZMOVE_CHECK_PULLEDBY) + +/// Flags used in "Move Upwards" and "Move Downwards" verbs. +#define ZMOVE_FLIGHT_FLAGS (ZMOVE_CAN_FLY_CHECKS|ZMOVE_INCAPACITATED_CHECKS|ZMOVE_CHECK_PULLS|ZMOVE_ALLOW_BUCKLED) +/// Used when walking upstairs +#define ZMOVE_STAIRS_FLAGS (ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED) +/// Used for falling down open space. +#define ZMOVE_FALL_FLAGS (ZMOVE_FALL_CHECKS|ZMOVE_ALLOW_BUCKLED) diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm index 6d3e04cb2f4..bd41f1dc2ab 100644 --- a/code/controllers/subsystem/throwing.dm +++ b/code/controllers/subsystem/throwing.dm @@ -195,10 +195,9 @@ SUBSYSTEM_DEF(throwing) if (callback) callback.Invoke() - if(!thrownthing.zfalling) // I don't think you can zfall while thrown but hey, just in case. + if(!thrownthing.currently_z_moving) // I don't think you can zfall while thrown but hey, just in case. var/turf/T = get_turf(thrownthing) - if(T && thrownthing.has_gravity(T)) - T.zFall(thrownthing) + T?.zFall(thrownthing) if(thrownthing) SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src) diff --git a/code/datums/components/omen.dm b/code/datums/components/omen.dm index d22bed870c6..da6437d1b1e 100644 --- a/code/datums/components/omen.dm +++ b/code/datums/components/omen.dm @@ -71,10 +71,8 @@ qdel(src) return - for(var/t in get_adjacent_open_turfs(living_guy)) - var/turf/the_turf = t - - if(the_turf.zPassOut(living_guy, DOWN) && living_guy.can_zFall(the_turf)) + for(var/turf/the_turf as anything in get_adjacent_open_turfs(living_guy)) + if(the_turf.zPassOut(living_guy, DOWN) && living_guy.can_z_move(DOWN, the_turf, z_move_flags = ZMOVE_FALL_FLAGS)) to_chat(living_guy, span_warning("A malevolent force guides you towards the edge...")) living_guy.throw_at(the_turf, 1, 10, force = MOVE_FORCE_EXTREMELY_STRONG) if(!permanent) diff --git a/code/datums/components/riding/riding.dm b/code/datums/components/riding/riding.dm index c53814ffa6d..e6adb2d19c6 100644 --- a/code/datums/components/riding/riding.dm +++ b/code/datums/components/riding/riding.dm @@ -63,6 +63,7 @@ RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved) RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/vehicle_bump) + RegisterSignal(parent, COMSIG_BUCKLED_CAN_Z_MOVE, .proc/riding_can_z_move) /** * This proc handles all of the proc calls to things like set_vehicle_dir_layer() that a type of riding datum needs to call on creation @@ -128,9 +129,12 @@ vehicle_moved(source, null, new_dir) -/// Check to see if we have all of the necessary bodyparts and not-falling-over statuses we need to stay onboard -/datum/component/riding/proc/ride_check(mob/living/rider) - return +/** + * Check to see if we have all of the necessary bodyparts and not-falling-over statuses we need to stay onboard. + * If not and if consequences is TRUE, well, there'll be consequences. + */ +/datum/component/riding/proc/ride_check(mob/living/rider, consequences = TRUE) + return TRUE /datum/component/riding/proc/handle_vehicle_offsets(dir) var/atom/movable/AM = parent @@ -246,3 +250,8 @@ else qdel(O) return TRUE + +/// Extra checks before buckled.can_z_move can be called in mob/living/can_z_move() +/datum/component/riding/proc/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider) + SIGNAL_HANDLER + return COMPONENT_RIDDEN_ALLOW_Z_MOVE diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm index b517c9c5e89..5a4ee7fbe1b 100644 --- a/code/datums/components/riding/riding_mob.dm +++ b/code/datums/components/riding/riding_mob.dm @@ -47,21 +47,21 @@ rider.log_message("started riding [living_parent]", LOG_ATTACK, color="pink") // this applies to humans and most creatures, but is replaced again for cyborgs -/datum/component/riding/creature/ride_check(mob/living/rider) +/datum/component/riding/creature/ride_check(mob/living/rider, consequences = TRUE) + . = TRUE var/mob/living/living_parent = parent - var/kick_us_off if(living_parent.body_position != STANDING_UP) // if we move while on the ground, the rider falls off - kick_us_off = TRUE + . = FALSE // for piggybacks and (redundant?) borg riding, check if the rider is stunned/restrained else if((ride_check_flags & RIDER_NEEDS_ARMS) && (HAS_TRAIT(rider, TRAIT_RESTRAINED) || rider.incapacitated(TRUE, TRUE))) - kick_us_off = TRUE + . = FALSE // for fireman carries, check if the ridden is stunned/restrained else if((ride_check_flags & CARRIER_NEEDS_ARM) && (HAS_TRAIT(living_parent, TRAIT_RESTRAINED) || living_parent.incapacitated(TRUE, TRUE))) - kick_us_off = TRUE + . = FALSE - if(!kick_us_off) - return TRUE + if(. || !consequences) + return rider.visible_message(span_warning("[rider] falls off of [living_parent]!"), \ span_warning("You fall off of [living_parent]!")) @@ -158,6 +158,20 @@ proc_holder.remove_ranged_ability() proc_holder.action.Unshare(rider) +/datum/component/riding/creature/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider) + if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS)) + return COMPONENT_RIDDEN_ALLOW_Z_MOVE + if(!can_be_driven) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "[movable_parent] cannot be driven around. Unbuckle from [movable_parent.p_them()] first.") + return COMPONENT_RIDDEN_STOP_Z_MOVE + if(!ride_check(rider, FALSE)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "You're unable to ride [movable_parent] right now!") + return COMPONENT_RIDDEN_STOP_Z_MOVE + return COMPONENT_RIDDEN_ALLOW_Z_MOVE + + ///////Yes, I said humans. No, this won't end well...////////// /datum/component/riding/creature/human can_be_driven = FALSE @@ -259,12 +273,12 @@ /datum/component/riding/creature/cyborg can_be_driven = FALSE -/datum/component/riding/creature/cyborg/ride_check(mob/living/user) +/datum/component/riding/creature/cyborg/ride_check(mob/living/user, consequences = TRUE) var/mob/living/silicon/robot/robot_parent = parent if(!iscarbon(user)) - return - var/mob/living/carbon/carbonuser = user - if(!carbonuser.usable_hands) + return TRUE + . = user.usable_hands + if(!. && consequences) Unbuckle(user) to_chat(user, span_warning("You can't grab onto [robot_parent] with no hands!")) diff --git a/code/datums/components/riding/riding_vehicle.dm b/code/datums/components/riding/riding_vehicle.dm index f5c25585d5f..68d9fa36c5a 100644 --- a/code/datums/components/riding/riding_vehicle.dm +++ b/code/datums/components/riding/riding_vehicle.dm @@ -9,6 +9,29 @@ . = ..() RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, .proc/driver_move) +/datum/component/riding/vehicle/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider) + if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS)) + return COMPONENT_RIDDEN_ALLOW_Z_MOVE + + if(!keycheck(rider)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "[movable_parent] has no key inserted!") + return COMPONENT_RIDDEN_STOP_Z_MOVE + if(HAS_TRAIT(rider, TRAIT_INCAPACITATED)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "You cannot operate [movable_parent] right now!") + return COMPONENT_RIDDEN_STOP_Z_MOVE + if(ride_check_flags & RIDER_NEEDS_LEGS && HAS_TRAIT(rider, TRAIT_FLOORED)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "You can't seem to manage that while unable to stand up enough to move [movable_parent]...") + return COMPONENT_RIDDEN_STOP_Z_MOVE + if(ride_check_flags & RIDER_NEEDS_ARMS && HAS_TRAIT(rider, TRAIT_HANDS_BLOCKED)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider, "You can't seem to hold onto [movable_parent] to move it...") + return COMPONENT_RIDDEN_STOP_Z_MOVE + + return COMPONENT_RIDDEN_ALLOW_Z_MOVE + /datum/component/riding/vehicle/driver_move(atom/movable/movable_parent, mob/living/user, direction) if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown)) return COMPONENT_DRIVER_BLOCK_MOVE diff --git a/code/datums/elements/movetype_handler.dm b/code/datums/elements/movetype_handler.dm index d3da745fbce..f1c6486d7bd 100644 --- a/code/datums/elements/movetype_handler.dm +++ b/code/datums/elements/movetype_handler.dm @@ -69,6 +69,9 @@ source.movement_type &= ~flag if((old_state & (FLOATING|FLYING)) && !(source.movement_type & (FLOATING|FLYING))) stop_floating(source) + var/turf/pitfall = source.loc //Things that don't fly fall in open space. + if(istype(pitfall)) + pitfall.zFall(source) SEND_SIGNAL(source, COMSIG_MOVETYPE_FLAG_DISABLED, flag, old_state) /// Called when the TRAIT_NO_FLOATING_ANIM trait is added to the movable. Stops it from bobbing up and down. diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index c165ae480df..189a32baa6e 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -9,7 +9,7 @@ #define HOLORECORD_MAX_LENGTH 200 -/mob/camera/ai_eye/remote/holo/setLoc() +/mob/camera/ai_eye/remote/holo/setLoc(turf/destination, force_update = FALSE) . = ..() var/obj/machinery/holopad/H = origin H?.move_hologram(eye_user, loc) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 0c34ecf1f01..0a7a9a8b423 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1785,8 +1785,8 @@ filter_data = null filters = null -/atom/proc/intercept_zImpact(atom/movable/hitting_atom, levels = 1) - . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, hitting_atom, levels) +/atom/proc/intercept_zImpact(list/falling_movables, levels = 1) + . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, falling_movables, levels) /// Sets the custom materials for an item. /atom/proc/set_custom_materials(list/materials, multiplier = 1) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index f6fb859c7c3..54d818ac86e 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -56,9 +56,9 @@ var/grab_state = 0 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 = FALSE @@ -167,20 +167,9 @@ if(emissive_block) . += emissive_block -/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction) - if(!direction) - direction = DOWN - if(!source) - source = get_turf(src) - if(!source) - return FALSE - if(!target) - target = get_step_multiz(source, direction) - if(!target) - return FALSE - return !(movement_type & FLYING) && has_gravity(source) && !throwing - -/atom/movable/proc/onZImpact(turf/impacted_turf, levels) +/atom/movable/proc/onZImpact(turf/impacted_turf, levels, message = TRUE) + if(message) + visible_message("[src] crashes into [impacted_turf]!") var/atom/highest = impacted_turf for(var/atom/hurt_atom as anything in impacted_turf.contents) if(!hurt_atom.density) @@ -191,23 +180,94 @@ INVOKE_ASYNC(src, .proc/SpinAnimation, 5, 2) return TRUE -//For physical constraints to travelling up/down. -/atom/movable/proc/can_zTravel(turf/destination, direction) - var/turf/current_turf = get_turf(src) - if(!current_turf) - return FALSE +/* + * 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(current_turf, destination) + direction = get_dir_multiz(start, destination) if(direction != UP && direction != DOWN) return FALSE if(!destination) - destination = get_step_multiz(src, direction) + destination = get_step_multiz(start, direction) if(!destination) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, "There's nowhere to go in that direction!") return FALSE - return current_turf.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, current_turf) - + 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, "[src] is is not capable of flight.") + else + to_chat(src, "You are not Superman.") + return FALSE + if(!(z_move_flags & ZMOVE_IGNORE_OBSTACLES) && !(start.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, start))) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, "You couldn't move there!") + return FALSE + return destination //used by some child types checks and zMove() /atom/movable/vv_edit_var(var_name, var_value) var/static/list/banned_edits = list("step_x" = TRUE, "step_y" = TRUE, "step_size" = TRUE, "bounds" = TRUE) @@ -336,23 +396,22 @@ var/mob/living/pulled_mob = moving_atom set_pull_offsets(pulled_mob, grab_state) -/atom/movable/proc/check_pulling() +/** + * 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) - var/atom/movable/pullee = pulling - if(pullee && get_dist(src, pullee) > 1) + if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) stop_pulling() - return - if(!isturf(loc)) + else if(!isturf(loc)) stop_pulling() - return - if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") + 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() - return - if(pulling.anchored || pulling.move_resist > move_force) + else if(pulling.anchored || pulling.move_resist > move_force) stop_pulling() - return - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. + 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() @@ -457,7 +516,7 @@ var/atom/movable/pullee = pulling var/turf/current_turf = loc if(!moving_from_pull) - check_pulling() + check_pulling(z_allowed = TRUE) if(!loc || !newloc) return FALSE var/atom/oldloc = loc @@ -524,18 +583,26 @@ 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 - var/pull_dir = get_dir(src, pulling) //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 && (get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))) - pulling.moving_from_pull = src - pulling.Move(current_turf, get_dir(pulling, current_turf), glide_size) //the pullee tries to reach our previous position - pulling.moving_from_pull = null + 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() @@ -549,8 +616,20 @@ if(set_dir_on_move) setDir(direct) if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) - return FALSE + . = 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. @@ -586,7 +665,6 @@ 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/crossed_atom) @@ -704,6 +782,15 @@ anchored = anchorvalue SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue) +/// 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/proc/forceMove(atom/destination) . = FALSE if(destination) @@ -719,7 +806,8 @@ move_stacks++ var/atom/oldloc = loc if(destination) - if(pulledby) + ///zMove already handles whether a pull from another movable should be broken. + if(pulledby && !currently_z_moving) pulledby.stop_pulling() var/same_loc = oldloc == destination var/area/old_area = get_area(oldloc) @@ -924,7 +1012,7 @@ /atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override) for(var/mob/living/buckled_mob as anything in buckled_mobs) - if(!buckled_mob.Move(newloc, direct, glide_size_override)) + if(buckled_mob.loc != newloc && !buckled_mob.Move(newloc, direct, glide_size_override)) Move(buckled_mob.loc, direct) last_move = buckled_mob.last_move inertia_dir = last_move diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index da3a079890d..ca452f28f4f 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -209,13 +209,7 @@ return eye_user.client return null -/mob/camera/ai_eye/remote/xenobio/canZMove(direction, turf/target) - var/area/new_area = get_area(target) - if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) - return TRUE - return FALSE - -/mob/camera/ai_eye/remote/setLoc(destination) +/mob/camera/ai_eye/remote/setLoc(turf/destination, force_update = FALSE) if(eye_user) destination = get_turf(destination) if (destination) @@ -315,7 +309,7 @@ return var/mob/living/user_mob = target var/mob/camera/ai_eye/remote/remote_eye = user_mob.remote_control - if(remote_eye.zMove(UP, FALSE)) + if(remote_eye.zMove(UP)) to_chat(user_mob, span_notice("You move upwards.")) else to_chat(user_mob, span_notice("You couldn't move upwards!")) @@ -330,7 +324,7 @@ return var/mob/living/user_mob = target var/mob/camera/ai_eye/remote/remote_eye = user_mob.remote_control - if(remote_eye.zMove(DOWN, FALSE)) + if(remote_eye.zMove(DOWN)) to_chat(user_mob, span_notice("You move downwards.")) else to_chat(user_mob, span_notice("You couldn't move downwards!")) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 6b1e12fd4a1..56880932857 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -138,7 +138,7 @@ * buckled_mob - The mob to be unbuckled * force - TRUE if we should ignore buckled_mob.can_buckle_to */ -/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force = FALSE) +/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) if(!isliving(buckled_mob)) CRASH("Non-living [buckled_mob] thing called unbuckle_mob() for source.") if(buckled_mob.buckled != src) @@ -157,12 +157,17 @@ UnregisterSignal(src, COMSIG_MOVABLE_SET_ANCHORED) SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, buckled_mob, force) - var/turf/location = buckled_mob.loc - if(istype(location) && !buckled_mob.zfalling) - location.zFall(buckled_mob) + if(can_fall) + var/turf/location = buckled_mob.loc + if(istype(location) && !buckled_mob.currently_z_moving) + location.zFall(buckled_mob) post_unbuckle_mob(.) + if(!QDELETED(buckled_mob) && !buckled_mob.currently_z_moving && isturf(buckled_mob.loc)) // In the case they unbuckled to a flying movable midflight. + var/turf/pitfall = buckled_mob.loc + pitfall?.zFall(buckled_mob) + /atom/movable/proc/on_set_anchored(atom/movable/source, anchorvalue) SIGNAL_HANDLER for(var/_buckled_mob in buckled_mobs) diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index c1288d081e3..1fdc907798b 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -93,7 +93,7 @@ /obj/item/storage/box/mime/Moved(oldLoc, dir) if (iscarbon(oldLoc)) alpha = 0 - ..() + return ..() //Disk boxes diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm index 9ff21e4bcf8..fc9105bd9fd 100644 --- a/code/game/objects/structures/bonfire.dm +++ b/code/game/objects/structures/bonfire.dm @@ -164,7 +164,7 @@ if(..()) buckled_mob.pixel_y += 13 -/obj/structure/bonfire/unbuckle_mob(mob/living/buckled_mob, force=FALSE) +/obj/structure/bonfire/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) if(..()) buckled_mob.pixel_y -= 13 diff --git a/code/game/objects/structures/construction_console/construction_console.dm b/code/game/objects/structures/construction_console/construction_console.dm index fc35f3726e0..f9321db279e 100644 --- a/code/game/objects/structures/construction_console/construction_console.dm +++ b/code/game/objects/structures/construction_console/construction_console.dm @@ -118,7 +118,7 @@ return INITIALIZE_HINT_QDEL return ..() -/mob/camera/ai_eye/remote/base_construction/setLoc(destination) +/mob/camera/ai_eye/remote/base_construction/setLoc(turf/destination, force_update = FALSE) var/area/curr_area = get_area(destination) //Only move if we're in the allowed area. If no allowed area is defined, then we're free to move wherever. if(!linked_console.allowed_area || istype(curr_area, linked_console.allowed_area)) diff --git a/code/game/objects/structures/deployable_turret.dm b/code/game/objects/structures/deployable_turret.dm index 1a726b8d050..01ea1a1b120 100644 --- a/code/game/objects/structures/deployable_turret.dm +++ b/code/game/objects/structures/deployable_turret.dm @@ -60,7 +60,7 @@ //BUCKLE HOOKS -/obj/machinery/deployable_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) +/obj/machinery/deployable_turret/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) for(var/obj/item/I in buckled_mob.held_items) if(istype(I, /obj/item/gun_control)) diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index fece86325d0..ae4b282ef86 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -77,21 +77,9 @@ return show_fluff_message(going_up, user) - - var/turf/T = get_turf(ladder) - var/atom/movable/AM - if(user.pulling) - AM = user.pulling - AM.forceMove(T) - user.forceMove(T) - if(AM) - user.start_pulling(AM) - - //reopening ladder radial menu ahead - T = get_turf(user) - var/obj/structure/ladder/ladder_structure = locate() in T - if (ladder_structure) - ladder_structure.use(user) + var/turf/target = get_turf(ladder) + user.zMove(target = target, z_move_flags = ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED|ZMOVE_INCLUDE_PULLED) + ladder.use(user) //reopening ladder radial menu ahead /obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE) if (!is_ghost && !in_range(src, user)) diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 4865fbdc816..d4d248437c3 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -64,9 +64,10 @@ SIGNAL_HANDLER if(leaving == src) - return // Let's not block ourselves. + return //Let's not block ourselves. if(!isobserver(leaving) && isTerminator() && direction == dir) + leaving.set_currently_z_moving(CURRENTLY_Z_ASCENDING) INVOKE_ASYNC(src, .proc/stair_ascend, leaving) leaving.Bump(src) return COMPONENT_ATOM_BLOCK_EXIT @@ -80,23 +81,20 @@ icon_state = "stairs[isTerminator() ? "_t" : null]" return ..() -/obj/structure/stairs/proc/stair_ascend(atom/movable/AM) +/obj/structure/stairs/proc/stair_ascend(atom/movable/climber) var/turf/checking = get_step_multiz(get_turf(src), UP) if(!istype(checking)) return - if(!checking.zPassIn(AM, UP, get_turf(src))) + if(!checking.zPassIn(climber, UP, get_turf(src))) return var/turf/target = get_step_multiz(get_turf(src), (dir|UP)) - if(istype(target) && !target.can_zFall(AM, null, get_step_multiz(target, DOWN))) //Don't throw them into a tile that will just dump them back down. - if(isliving(AM)) - var/mob/living/L = AM - var/pulling = L.pulling - if(pulling) - L.pulling.forceMove(target) - L.forceMove(target) - L.start_pulling(pulling) - else - AM.forceMove(target) + if(istype(target) && !climber.can_z_move(DOWN, target, z_move_flags = ZMOVE_FALL_FLAGS)) //Don't throw them into a tile that will just dump them back down. + climber.zMove(target = target, z_move_flags = ZMOVE_STAIRS_FLAGS) + /// Moves anything that's being dragged by src or anything buckled to it to the stairs turf. + climber.pulling?.move_from_pull(climber, loc, climber.glide_size) + for(var/mob/living/buckled as anything in climber.buckled_mobs) + buckled.pulling?.move_from_pull(buckled, loc, buckled.glide_size) + /obj/structure/stairs/vv_edit_var(var_name, var_value) . = ..() @@ -132,10 +130,10 @@ if(T && !istype(T)) T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR) -/obj/structure/stairs/intercept_zImpact(atom/movable/AM, levels = 1) +/obj/structure/stairs/intercept_zImpact(list/falling_movables, levels = 1) . = ..() - if(isTerminator()) - . |= FALL_INTERCEPTED | FALL_NO_MESSAGE + if(levels == 1 && isTerminator()) // Stairs won't save you from a steep fall. + . |= FALL_INTERCEPTED | FALL_NO_MESSAGE | FALL_RETAIN_PULL /obj/structure/stairs/proc/isTerminator() //If this is the last stair in a chain and should move mobs up if(terminator_mode != STAIR_TERMINATOR_AUTOMATIC) diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index a1b6ef1c0e1..326b4849581 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -32,12 +32,49 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr /turf/open/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker . = ..() overlays += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it. + RegisterSignal(src, COMSIG_ATOM_CREATED, .proc/on_atom_created) return INITIALIZE_HINT_LATELOAD /turf/open/openspace/LateInitialize() . = ..() AddElement(/datum/element/turf_z_transparency, FALSE) +/turf/open/openspace/ChangeTurf(path, list/new_baseturfs, flags) + UnregisterSignal(src, COMSIG_ATOM_CREATED) + return ..() + +/** + * Prepares a moving movable to be precipitated if Move() is successful. + * This is done in Enter() and not Entered() because there's no easy way to tell + * if the latter was called by Move() or forceMove() while the former is only called by Move(). + */ +/turf/open/openspace/Enter(atom/movable/movable, atom/oldloc) + . = ..() + if(.) + //higher priority than CURRENTLY_Z_FALLING so the movable doesn't fall on Entered() + movable.set_currently_z_moving(CURRENTLY_Z_FALLING_FROM_MOVE) + +///Makes movables fall when forceMove()'d to this turf. +/turf/open/openspace/Entered(atom/movable/movable) + . = ..() + if(movable.set_currently_z_moving(CURRENTLY_Z_FALLING)) + zFall(movable, falling_from_move = TRUE) +/** + * Drops movables spawned on this turf only after they are successfully initialized. + * so flying mobs, qdeleted movables and things that were moved somewhere else during + * Initialize() won't fall by accident. + */ +/turf/open/openspace/proc/on_atom_created(datum/source, atom/created_atom) + SIGNAL_HANDLER + if(ismovable(created_atom)) + //Drop it only when it's finished initializing, not before. + addtimer(CALLBACK(src, .proc/zfall_if_on_turf, created_atom), 0 SECONDS) + +/turf/open/openspace/proc/zfall_if_on_turf(atom/movable/movable) + if(QDELETED(movable) || movable.loc != src) + return + zFall(movable) + /turf/open/openspace/can_have_cabling() if(locate(/obj/structure/lattice/catwalk, src)) return TRUE diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 655ad37c32d..d5a8ae80a88 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -172,7 +172,7 @@ if(!arrived || src != arrived.loc) return - if(destination_z && destination_x && destination_y && !(arrived.pulledby || !arrived.can_be_z_moved)) + if(destination_z && destination_x && destination_y && !arrived.pulledby && !arrived.currently_z_moving) var/tx = destination_x var/ty = destination_y var/turf/DT = locate(tx, ty, destination_z) @@ -191,21 +191,13 @@ ty-- DT = locate(tx, ty, destination_z) - var/atom/movable/pulling = arrived.pulling - var/atom/movable/puller = arrived - arrived.forceMove(DT) + arrived.zMove(null, DT, ZMOVE_ALLOW_BUCKLED) - while (pulling != null) - var/next_pulling = pulling.pulling - - var/turf/T = get_step(puller.loc, turn(puller.dir, 180)) - pulling.can_be_z_moved = FALSE - pulling.forceMove(T) - puller.start_pulling(pulling) - pulling.can_be_z_moved = TRUE - - puller = pulling - pulling = next_pulling + var/atom/movable/current_pull = arrived.pulling + while (current_pull) + var/turf/target_turf = get_step(current_pull.pulledby.loc, REVERSE_DIR(current_pull.pulledby.dir)) || current_pull.pulledby.loc + current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED) + current_pull = current_pull.pulling /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 565d7587152..f7bb75b4faf 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -260,38 +260,56 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/proc/zAirOut(direction, turf/source) return FALSE -/turf/proc/zImpact(atom/movable/A, levels = 1, turf/prev_turf) +/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() +/turf/proc/zFall(atom/movable/falling, levels = 1, force = FALSE, falling_from_move = FALSE) + var/turf/target = get_step_multiz(src, DOWN) + if(!target) + return FALSE + var/isliving = isliving(falling) + if(!isliving && !isobj(falling)) + return + if(isliving) + var/mob/living/falling_living = falling + //relay this mess to whatever the mob is buckled to. + if(falling_living.buckled) + falling = falling_living.buckled + if(!falling_from_move && falling.currently_z_moving) + return + if(!force && !falling.can_z_move(DOWN, src, target, ZMOVE_FALL_FLAGS)) + falling.set_currently_z_moving(FALSE, TRUE) + return FALSE + + // So it doesn't trigger other zFall calls. Cleared on zMove. + falling.set_currently_z_moving(CURRENTLY_Z_FALLING) + + falling.zMove(null, target, ZMOVE_CHECK_PULLEDBY) + target.zImpact(falling, levels, src) + return TRUE + +///Called each time the target falls down a z level possibly making their trajectory come to a halt. see __DEFINES/movement.dm. +/turf/proc/zImpact(atom/movable/falling, levels = 1, turf/prev_turf) var/flags = NONE - var/mov_name = A.name + var/list/falling_movables = falling.get_z_move_affected() + var/list/falling_mov_names + for(var/atom/movable/falling_mov as anything in falling_movables) + falling_mov_names += falling_mov.name for(var/i in contents) var/atom/thing = i - flags |= thing.intercept_zImpact(A, levels) + flags |= thing.intercept_zImpact(falling_movables, levels) if(flags & FALL_STOP_INTERCEPTING) break if(prev_turf && !(flags & FALL_NO_MESSAGE)) - prev_turf.visible_message(span_danger("[mov_name] falls through [prev_turf]!")) - if(flags & FALL_INTERCEPTED) - return - if(zFall(A, levels + 1)) + for(var/mov_name in falling_mov_names) + prev_turf.visible_message(span_danger("[mov_name] falls through [prev_turf]!")) + if(!(flags & FALL_INTERCEPTED) && zFall(falling, levels + 1)) return FALSE - A.visible_message(span_danger("[A] crashes into [src]!")) - A.onZImpact(src, levels) - return TRUE - -/turf/proc/can_zFall(atom/movable/A, levels = 1, turf/target) - SHOULD_BE_PURE(TRUE) - return zPassOut(A, DOWN, target) && target.zPassIn(A, DOWN, src) - -/turf/proc/zFall(atom/movable/A, levels = 1, force = FALSE) - var/turf/target = get_step_multiz(src, DOWN) - if(!target || (!isobj(A) && !ismob(A))) - return FALSE - if(!force && (!can_zFall(A, levels, target) || !A.can_zFall(src, levels, target, DOWN))) - return FALSE - A.zfalling = TRUE - A.forceMove(target) - A.zfalling = FALSE - target.zImpact(A, levels, src) + for(var/atom/movable/falling_mov as anything in falling_movables) + if(!(flags & FALL_RETAIN_PULL)) + falling_mov.stop_pulling() + if(!(flags & FALL_INTERCEPTED)) + falling_mov.onZImpact(src, levels) + if(falling_mov.pulledby && (falling_mov.z != falling_mov.pulledby.z || get_dist(falling_mov, falling_mov.pulledby) > 1)) + falling_mov.pulledby.stop_pulling() return TRUE /turf/proc/handleRCL(obj/item/rcl/C, mob/user) @@ -361,14 +379,12 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/open/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) - ..() + . = ..() //melting if(isobj(arrived) && air && air.temperature > T0C) var/obj/O = arrived if(O.obj_flags & FROZEN) O.make_unfrozen() - if(!arrived.zfalling) - zFall(arrived) // A proc in case it needs to be recreated or badmins want to change the baseturfs /turf/proc/assemble_baseturfs(turf/fake_baseturf_type) diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm index 586400f3b41..64f46459229 100644 --- a/code/modules/antagonists/blob/overmind.dm +++ b/code/modules/antagonists/blob/overmind.dm @@ -106,8 +106,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) if(blobstrain.effectdesc) to_chat(src, "The [blobstrain.name] strain [blobstrain.effectdesc]") -/mob/camera/blob/can_zFall(turf/source, levels) - // Prevent blob from falling through zlevels +/mob/camera/blob/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) return FALSE /mob/camera/blob/proc/is_valid_turf(turf/T) diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm index 6185b91b30e..e2bff938890 100644 --- a/code/modules/antagonists/disease/disease_mob.dm +++ b/code/modules/antagonists/disease/disease_mob.dm @@ -134,6 +134,9 @@ the new instance inside the host to be updated to the template's stats. follow_next(Dir & NORTHWEST) last_move_tick = world.time +/mob/camera/disease/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) + return FALSE + /mob/camera/disease/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list()) . = ..() var/atom/movable/to_follow = speaker diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm index daf00cae2f9..71d04598086 100644 --- a/code/modules/awaymissions/away_props.dm +++ b/code/modules/awaymissions/away_props.dm @@ -98,7 +98,7 @@ update_openspace() var/turf/T = get_turf(src) for(var/atom/movable/AM in T) - if(!AM.zfalling) + if(!AM.currently_z_moving) T.zFall(AM) /obj/structure/pitgrate/proc/reset_plane() diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm index f3e411fb06a..9e156913a98 100644 --- a/code/modules/mob/dead/observer/observer_movement.dm +++ b/code/modules/mob/dead/observer/observer_movement.dm @@ -1,2 +1,11 @@ -/mob/dead/observer/canZMove(direction, turf/target) - return TRUE +/mob/dead/observer/up() + set name = "Move Upwards" + set category = "IC" + + if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + to_chat(src, "You move upwards.") + +/mob/dead/observer/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) + z_move_flags |= ZMOVE_IGNORE_OBSTACLES //observers do not respect these FLOORS you speak so much of. + return ..() + diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2d0e3b38783..3eb751da791 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -41,9 +41,10 @@ QDEL_LAZYLIST(diseases) return ..() -/mob/living/onZImpact(turf/T, levels) +/mob/living/onZImpact(turf/T, levels, message = TRUE) if(!isgroundlessturf(T)) ZImpactDamage(T, levels) + message = FALSE return ..() /mob/living/proc/ZImpactDamage(turf/T, levels) @@ -836,9 +837,10 @@ lying_angle_on_movement(direct) if (buckled && buckled.loc != newloc) //not updating position if (!buckled.anchored) - return buckled.Move(newloc, direct, glide_size) - else - return FALSE + buckled.moving_from_pull = moving_from_pull + . = buckled.Move(newloc, direct, glide_size) + buckled.moving_from_pull = null + return var/old_direction = dir var/turf/T = loc @@ -848,12 +850,9 @@ . = ..() - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1 && (pulledby != moving_from_pull))//separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - else - if(isliving(pulledby)) - var/mob/living/L = pulledby - L.set_pull_offsets(src, pulledby.grab_state) + if(moving_diagonally != FIRST_DIAG_STEP && isliving(pulledby)) + var/mob/living/L = pulledby + L.set_pull_offsets(src, pulledby.grab_state) if(active_storage && !(CanReach(active_storage.parent,view_only = TRUE))) active_storage.close(src) @@ -1565,15 +1564,15 @@ return L /mob/living/forceMove(atom/destination) - stop_pulling() - if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) - buckled.unbuckle_mob(src, force = TRUE) - if(has_buckled_mobs()) - unbuckle_all_mobs(force = TRUE) + if(!currently_z_moving) + stop_pulling() + if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) + buckled.unbuckle_mob(src, force = TRUE) + if(has_buckled_mobs()) + unbuckle_all_mobs(force = TRUE) . = ..() - if(.) - if(client) - reset_perspective() + if(. && client) + reset_perspective() /mob/living/proc/update_z(new_z) // 1+ to register, null to unregister diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index 0e224908382..dcefeca01f6 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -58,10 +58,48 @@ return remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) -/mob/living/canZMove(dir, turf/target) +/** + * We want to relay the zmovement to the buckled atom when possible + * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here. + * This way we can avoid esoteric bugs, copypasta and inconsistencies. + */ +/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) if(buckled) - return buckled.can_zTravel(target, dir) && (buckled.movement_type & FLYING | FLOATING) - return can_zTravel(target, dir) && (movement_type & FLYING | FLOATING) + if(buckled.currently_z_moving) + return FALSE + if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED)) + buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE) + else + if(!target) + target = can_z_move(dir, get_turf(src), null, z_move_flags, src) + if(!target) + return FALSE + return buckled.zMove(dir, target, z_move_flags) // Return value is a loc. + return ..() + +/mob/living/can_z_move(direction, turf/start, turf/destination, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider) + if(z_move_flags & ZMOVE_INCAPACITATED_CHECKS && incapacitated()) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, "[rider ? src : "You"] can't do that right now!") + return FALSE + if(!buckled || !(z_move_flags & ZMOVE_ALLOW_BUCKLED)) + return ..() + switch(SEND_SIGNAL(buckled, COMSIG_BUCKLED_CAN_Z_MOVE, direction, start, destination, z_move_flags, src)) + if(COMPONENT_RIDDEN_ALLOW_Z_MOVE) // Can be ridden. + return buckled.can_z_move(direction, start, destination, z_move_flags, src) + if(COMPONENT_RIDDEN_STOP_Z_MOVE) // Is a ridable but can't be ridden right now. Feedback messages already done. + return FALSE + else + if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS) && !buckled.anchored) + return buckled.can_z_move(direction, start, destination, z_move_flags, src) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(src, "Unbuckle from [buckled] first.") + return FALSE + +/mob/set_currently_z_moving(value) + if(buckled) + return buckled.set_currently_z_moving(value) + return ..() /mob/living/keybind_face_direction(direction) if(stat > SOFT_CRIT) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 86ff84c24af..e6da2a6d412 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -999,12 +999,15 @@ set name = "Move Upwards" set category = "IC" - if(zMove(UP, TRUE)) + if(eyeobj.zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move upwards.")) -/mob/living/silicon/ai/zMove(dir, feedback = FALSE) - . = eyeobj.zMove(dir, feedback) +/mob/living/silicon/ai/down() + set name = "Move Down" + set category = "IC" + if(eyeobj.zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + to_chat(src, span_notice("You move down.")) /// Proc to hook behavior to the changes of the value of [aiRestorePowerRoutine]. /mob/living/silicon/ai/proc/setAiRestorePowerRoutine(new_value) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 058eb180313..3c4ae16317e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -101,24 +101,14 @@ if(ai.master_multicam) ai.master_multicam.refresh_view() -//it uses setLoc not forceMove, talks to the sillycone and not the camera mob -/mob/camera/ai_eye/zMove(dir, feedback = FALSE) - if(dir != UP && dir != DOWN) - return FALSE - var/turf/target = get_step_multiz(src, dir) - if(!target) - if(feedback) - to_chat(ai, span_warning("There's nowhere to go in that direction!")) - return FALSE - if(!canZMove(dir, target)) - if(feedback) - to_chat(ai, span_warning("You couldn't move there!")) - return FALSE - setLoc(target, TRUE) - return TRUE +/mob/camera/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) + . = ..() + if(.) + setLoc(loc, force_update = TRUE) -/mob/camera/ai_eye/canZMove(direction, turf/target) //cameras do not respect these FLOORS you speak so much of - return TRUE +/mob/camera/ai_eye/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) + z_move_flags |= ZMOVE_IGNORE_OBSTACLES //cameras do not respect these FLOORS you speak so much of + return ..() /mob/camera/ai_eye/Move() return diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm index 419ba2938b2..be0ea83b395 100644 --- a/code/modules/mob/living/silicon/ai/multicam.dm +++ b/code/modules/mob/living/silicon/ai/multicam.dm @@ -132,7 +132,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) if(screen?.ai) return screen.ai.client -/mob/camera/ai_eye/pic_in_pic/setLoc(turf/destination) +/mob/camera/ai_eye/pic_in_pic/setLoc(turf/destination, force_update = FALSE) if (destination) abstract_move(destination) else diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index fd2849dde5a..00885308073 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -510,13 +510,20 @@ var/turf/current_turf = get_turf(src) var/turf/above_turf = SSmapping.get_turf_above(current_turf) - var/ventcrawling_mob = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) - if(can_zFall(above_turf, 1, current_turf, DOWN) && !ventcrawling_mob) //Will be fall down if we go up? - to_chat(src, "You are not Superman.") + var/ventcrawling_flag = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) ? ZMOVE_VENTCRAWLING : 0 + if(!above_turf) + to_chat(src, "There's nowhere to go in that direction!") return - if(zMove(UP, TRUE, ventcrawling_mob)) + if(can_z_move(DOWN, above_turf, current_turf, ZMOVE_FALL_FLAGS|ventcrawling_flag)) //Will we fall down if we go up? + if(buckled) + to_chat(src, "[buckled] is is not capable of flight.") + else + to_chat(src, "You are not Superman.") + return + + if(zMove(UP, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK|ventcrawling_flag)) to_chat(src, span_notice("You move upwards.")) ///Moves a mob down a z level @@ -524,44 +531,10 @@ set name = "Move Down" set category = "IC" - var/ventcrawling_mob = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) - - if(zMove(DOWN, TRUE, ventcrawling_mob)) + var/ventcrawling_flag = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) ? ZMOVE_VENTCRAWLING : 0 + if(zMove(DOWN, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK|ventcrawling_flag)) to_chat(src, span_notice("You move down.")) - -/mob/can_zFall(turf/source, levels, turf/target, direction) - if(buckled) - return buckled.can_zFall(source, levels, target, direction) - return ..() - -///Move a mob between z levels, if it's valid to move z's on this turf -/mob/proc/zMove(dir, feedback = FALSE, ventcrawling = FALSE) - if(dir != UP && dir != DOWN) - return FALSE - if(incapacitated()) - if(feedback) - to_chat(src, span_warning("You can't do that right now!")) - return FALSE - var/turf/target = get_step_multiz(src, dir) - if(!target) - if(feedback) - to_chat(src, span_warning("There's nowhere to go in that direction!")) - return FALSE - if(!canZMove(dir, target) && !ventcrawling) - if(feedback) - to_chat(src, span_warning("You couldn't move there!")) - return FALSE - if(!ventcrawling) //let this be handled in atmosmachinery.dm - return Move(target) - else - var/obj/machinery/atmospherics/pipe = loc - pipe.relaymove(src, dir) - return TRUE - -/// Can this mob move between z levels -/mob/proc/canZMove(direction, turf/target) return FALSE - /mob/abstract_move(atom/destination) var/turf/new_turf = get_turf(destination) if(is_secret_level(new_turf.z) && !client?.holder) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 32e05c35a76..d49a04476d0 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -417,7 +417,7 @@ //BUCKLE HOOKS -/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob,force = 0) +/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) manual = FALSE for(var/obj/item/item in buckled_mob.held_items) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index f8190be37c7..2dd074f24b8 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -1085,9 +1085,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE) Consume(hit_object) -/obj/machinery/power/supermatter_crystal/intercept_zImpact(atom/movable/hit_object, levels) +/obj/machinery/power/supermatter_crystal/intercept_zImpact(list/falling_movables, levels) . = ..() - Bumped(hit_object) + for(var/atom/movable/hit_object as anything in falling_movables) + Bumped(hit_object) . |= FALL_STOP_INTERCEPTING | FALL_INTERCEPTED /obj/machinery/power/supermatter_crystal/proc/Consume(atom/movable/consumed_object) diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index 43bc421eed3..31dc8051818 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -10,13 +10,21 @@ allowed_area = A.name . = ..() -/mob/camera/ai_eye/remote/xenobio/setLoc(destination) +/mob/camera/ai_eye/remote/xenobio/setLoc(turf/destination, force_update = FALSE) var/area/new_area = get_area(destination) if(new_area && new_area.name == allowed_area || new_area && (new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) return ..() else return +/mob/camera/ai_eye/remote/xenobio/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider) + . = ..() + if(!.) + return + var/area/new_area = get_area(.) + if(new_area.name != allowed_area && !(new_area.area_flags & XENOBIOLOGY_COMPATIBLE)) + return FALSE + /obj/machinery/computer/camera_advanced/xenobio name = "Slime management console" desc = "A computer used for remotely handling slimes." diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index 2be48b1c461..f5c46b4c9aa 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -304,7 +304,7 @@ src.origin = origin return ..() -/mob/camera/ai_eye/remote/shuttle_docker/setLoc(destination) +/mob/camera/ai_eye/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE) . = ..() var/obj/machinery/computer/camera_advanced/shuttle_docker/console = origin console.checkLandingSpot() diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index fd438c75051..490481dcf80 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -651,7 +651,7 @@ GLOBAL_LIST_EMPTY(vending_products) to_chat(user, span_notice("You insert [I] into [src]'s input compartment.")) loaded_items++ -/obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force=FALSE) +/obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) if(!force) return . = ..()