diff --git a/code/__defines/turfs.dm b/code/__defines/turfs.dm
index ef1956bdb64..6a4602ab254 100644
--- a/code/__defines/turfs.dm
+++ b/code/__defines/turfs.dm
@@ -12,3 +12,8 @@
// Roof related flags
#define ROOF_FORCE_SPAWN 1
#define ROOF_CLEANUP 2
+
+// MultiZ faller control. (Bit flags.)
+// Default flag is needed for assoc lists to work.
+#define CLIMBER_DEFAULT 1
+#define CLIMBER_NO_EXIT 2
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 382391ab973..46539d51ed3 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -31,12 +31,12 @@
/proc/Centcomm_announce(var/msg, var/mob/Sender, var/iamessage)
var/msg_cciaa = "[uppertext(boss_short)][iamessage ? " IA" : ""]:[key_name(Sender, 1)] (RPLY): [msg]"
- msg = "[uppertext(boss_short)]M[iamessage ? " IA" : ""]:[key_name(Sender, 1)] (PP) (VV) (SM) ([admin_jump_link(Sender, src)]) (CA) (BSA) (RPLY): [msg]"
+ var/msg_admin = "[uppertext(boss_short)][iamessage ? " IA" : ""]:[key_name(Sender, 1)] (PP) (VV) (SM) ([admin_jump_link(Sender, src)]) (CA) (BSA) (RPLY): [msg]"
var/cciaa_present = 0
var/cciaa_afk = 0
for(var/client/C in admins)
if(R_ADMIN & C.holder.rights)
- C << msg
+ C << msg_admin
else if (R_CCIAA & C.holder.rights)
cciaa_present++
if (C.is_afk())
diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm
index a2c60aa10f5..b66d1c5929a 100644
--- a/code/modules/clothing/spacesuits/rig/modules/utility.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm
@@ -566,7 +566,7 @@
// Handle leaping at targets with a combat capable version here.
if (combatType && dist && (ismob(target) || (locate(/mob/living) in T)))
- H.leap(target, leapDistance)
+ H.do_leap(target, leapDistance, FALSE)
return 1
// If dist -> horizontal leap. Otherwise, the user clicked the turf that they're
@@ -600,7 +600,7 @@
// This setting is necessary even for combat type, to stop you from moving onto
// the turf, falling back down, and then getting forcemoved to the final destination.
- LAZYADD(TA.climbers, H)
+ TA.add_climber(H, CLIMBER_NO_EXIT)
H.forceMove(TA)
@@ -612,9 +612,13 @@
if (!do_after(H, 4 SECONDS, use_user_turf = TRUE))
H.visible_message("\The [H] is interrupted and falls!",
"You are interrupted and fall back down!")
- LAZYREMOVE(TA.climbers, H)
- ADD_FALLING_ATOM(H)
+ // Climbers will auto-fall if they exit the turf. This is for in case
+ // something else interrupts them.
+ if (H.loc == TA)
+ TA.remove_climber(H)
+ ADD_FALLING_ATOM(H)
+
return 1
H.visible_message("\The [H] finishes climbing onto \the [leapEnd].",
@@ -623,7 +627,7 @@
H.visible_message("\The [H] lands on \the [leapEnd] with a heavy slam!",
"You land on \the [leapEnd] with a heavy thud!")
- LAZYREMOVE(TA.climbers, H)
+ // open/Exited() removes from climbers.
H.forceMove(leapEnd)
return 1
diff --git a/code/modules/mob/freelook/ai/update_triggers.dm b/code/modules/mob/freelook/ai/update_triggers.dm
index 63ff0e292a5..2fe2a40a381 100644
--- a/code/modules/mob/freelook/ai/update_triggers.dm
+++ b/code/modules/mob/freelook/ai/update_triggers.dm
@@ -9,11 +9,11 @@
/mob/living/silicon/robot/Move()
var/oldLoc = src.loc
. = ..()
- //if(.)
- if(provides_camera_vision())
- if(!updating)
- updating = 1
- addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER)
+ if (.)
+ if(provides_camera_vision())
+ if(!updating)
+ updating = 1
+ addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER)
/mob/living/silicon/robot/proc/camera_post_move(oldLoc)
if (oldLoc != loc)
@@ -24,9 +24,9 @@
/mob/living/silicon/ai/Move()
var/oldLoc = src.loc
. = ..()
- //if(.)
- if(provides_camera_vision())
- addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER, TIMER_UNIQUE)
+ if (.)
+ if(provides_camera_vision())
+ addtimer(CALLBACK(src, .proc/camera_post_move, oldLoc), BORG_CAMERA_BUFFER, TIMER_UNIQUE)
/mob/living/silicon/ai/proc/camera_post_move(oldLoc)
if(oldLoc != src.loc)
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 3bdc97c1498..93203a05559 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -49,72 +49,79 @@
if ((O.client && !( O.blinded )))
O.show_message(text("[] [failed ? "tried to tackle" : "has tackled"] down []!", src, T), 1)
-/mob/living/carbon/human/proc/leap(var/mob/living/T = null, var/max_range = 4)
+/mob/living/carbon/human/proc/leap(mob/living/T as mob in view(4))
set category = "Abilities"
set name = "Leap"
set desc = "Leap at a target and grab them aggressively."
- if(last_special > world.time)
- return
+ do_leap(T)
+
+/mob/living/carbon/human/proc/do_leap(mob/living/T, max_range = 4, restrict_special = TRUE)
+ if(restrict_special && last_special > world.time)
+ src << "You're too tired to leap!"
+ return FALSE
+
+ if (status_flags & LEAPING)
+ src << "You're already leaping!"
+ return FALSE
if(stat || paralysis || stunned || weakened || lying || restrained() || buckled)
- src << "You cannot leap in your current state."
- return
+ src << "You cannot leap in your current state."
+ return FALSE
- if (!T)
+ if (!T || issilicon(T)) // Silicon targets require us to rebuild the list.
var/list/choices = list()
- for(var/mob/living/M in view(6,src))
+ for(var/mob/living/M in view(max_range, src))
if(!istype(M,/mob/living/silicon))
choices += M
choices -= src
T = input(src,"Who do you wish to leap at?") as null|anything in choices
- if(!T || !src || src.stat) return
+ if(!T || QDELETED(src) || stat)
+ return FALSE
- if(get_dist(get_turf(T), get_turf(src)) > max_range) return
+ if(get_dist(get_turf(T), get_turf(src)) > max_range)
+ src << "[T] is too far away!"
+ return FALSE
- if(last_special > world.time)
- return
+ if (restrict_special)
+ last_special = world.time + 75
- if(stat || paralysis || stunned || weakened || lying || restrained() || buckled)
- src << "You cannot leap in your current state."
- return
-
- last_special = world.time + 75
status_flags |= LEAPING
- src.visible_message("\The [src] leaps at [T]!")
- src.throw_at(get_step(get_turf(T),get_turf(src)), 4, 1, src)
+ visible_message("[src] leaps at [T]!", "You leap at [T]!")
+ throw_at(get_step(get_turf(T), get_turf(src)), 4, 1, src)
// Only Vox get to shriek. Seriously.
if (isvox(src))
- playsound(src.loc, 'sound/voice/shriek1.ogg', 50, 1)
+ playsound(loc, 'sound/voice/shriek1.ogg', 50, 1)
sleep(5)
- if(status_flags & LEAPING) status_flags &= ~LEAPING
+ if(status_flags & LEAPING)
+ status_flags &= ~LEAPING
if(!src.Adjacent(T))
src << "You miss!"
- return
+ return FALSE
T.Weaken(3)
// Pariahs are not good at leaping. This is snowflakey, pls fix.
if(species.name == "Vox Pariah")
src.Weaken(5)
- return
+ return TRUE
var/use_hand = "left"
if(l_hand)
if(r_hand)
src << "You need to have one hand free to grab someone."
- return
+ return TRUE
else
use_hand = "right"
- src.visible_message("\The [src] seizes [T] aggressively!")
+ visible_message("[src] seizes [T] aggressively!", "You aggressively seize [T]!")
var/obj/item/weapon/grab/G = new(src,T)
if(use_hand == "left")
@@ -126,6 +133,8 @@
G.icon_state = "grabbed1"
G.synch()
+ return TRUE
+
/mob/living/carbon/human/proc/gut()
set category = "Abilities"
set name = "Gut"
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 16901b62493..83d203ec19c 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -367,7 +367,7 @@
cameraFollow = null
/mob/living/silicon/Move(newloc, direct)
- ..(newloc,direct)
+ . = ..()
if (underdoor)
underdoor = 0
if ((layer == UNDERDOOR))//if this is false, then we must have used hide, or had our layer changed by something else. We wont do anymore checks for this move proc
diff --git a/code/modules/multiz/structures.dm b/code/modules/multiz/structures.dm
index 4ccc6cfd81b..5a0f53fa558 100644
--- a/code/modules/multiz/structures.dm
+++ b/code/modules/multiz/structures.dm
@@ -43,7 +43,9 @@
/obj/structure/ladder/attackby(obj/item/C as obj, mob/user as mob)
attack_hand(user)
- return
+
+/obj/structure/ladder/attack_robot(mob/user)
+ attack_hand(user)
/obj/structure/ladder/attack_hand(var/mob/M)
if(!M.may_climb_ladders(src))
@@ -102,6 +104,19 @@
return FALSE
return TRUE
+/mob/living/silicon/may_climb_ladders(ladder)
+ to_chat(src, "You're too heavy to climb [ladder]!")
+ return FALSE
+
+/mob/living/silicon/robot/drone/may_climb_ladders(ladder)
+ if(!Adjacent(ladder))
+ to_chat(src, "You need to be next to \the [ladder] to start climbing.")
+ return FALSE
+ if(incapacitated())
+ to_chat(src, "You are physically unable to climb \the [ladder].")
+ return FALSE
+ return TRUE
+
/mob/observer/ghost/may_climb_ladders(var/ladder)
return TRUE
diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm
index 48a3071f6b8..6e91657e5e5 100644
--- a/code/modules/multiz/turf.dm
+++ b/code/modules/multiz/turf.dm
@@ -54,10 +54,32 @@
if (mover.Check_Shoegrip(FALSE) && mover.can_fall(below, src))
to_chat(mover, span("notice",
"You are stopped from falling off the edge by \the [mover.shoes] you're wearing!"))
- return 0
+ return FALSE
return ..()
+// Add a falling atom by default. Even if it's not an atom that can actually fall.
+// SSfalling will check this on its own and remove if necessary. This is saner, as it
+// centralizes control to SSfalling.
+/turf/simulated/open/Entered(atom/movable/mover)
+ ..()
+ ADD_FALLING_ATOM(mover)
+ update_icon()
+
+// Override to deny a climber exit if they're set to adhere to CLIMBER_NO_EXIT
+/turf/simulated/open/Exit(atom/movable/mover, atom/newloc)
+ var/flags = remove_climber(mover)
+
+ if (flags & CLIMBER_NO_EXIT)
+ ADD_FALLING_ATOM(mover)
+ return FALSE
+
+ return ..()
+
+// Remove from climbers just in case.
+/turf/simulated/open/Exited(atom/movable/mover, atom/newloc)
+ LAZYREMOVE(climbers, mover)
+
/turf/simulated/open/Destroy()
SSopenturf.openspace_turfs -= src
SSopenturf.queued_turfs -= src
@@ -95,6 +117,42 @@
return FALSE
+/**
+ * Used to add a climber to the climbers list. Climbers do not fall down this specific tile.
+ *
+ * @param climber The atom to be added as a climber.
+ * @param flags Bitflags to control the status of the climber. Should always be non-0!
+ *
+ * @return TRUE if a climber was successfully added. FALSE if the climber is already
+ * present or an error occured.
+ */
+/turf/simulated/open/proc/add_climber(atom/climber, flags = CLIMBER_DEFAULT)
+ if (!flags)
+ PROCLOG_WEIRD("Attempted to add climber [climber] without flags.")
+ return FALSE
+
+ if (LAZYACCESS(climbers, climber))
+ return FALSE
+
+ LAZYINITLIST(climbers)
+ climbers[climber] = flags
+ return TRUE
+
+/**
+ * Used to remove a climber from the climbers list. Returns the flags the climber
+ * was assigned.
+ *
+ * @param climber The atom to be removed from climbers.
+ *
+ * @return The flags assigned to the climber if it was present in the list. 0 otherwise.
+ */
+/turf/simulated/open/proc/remove_climber(atom/climber)
+ . = 0
+
+ if (LAZYACCESS(climbers, climber))
+ . = climbers[climber]
+ LAZYREMOVE(climbers, climber)
+
/turf/simulated/open/airless
oxygen = 0
nitrogen = 0
@@ -127,11 +185,6 @@
/turf/simulated/open/update_dirt()
return 0
-/turf/simulated/open/Entered(atom/movable/mover)
- ..()
- ADD_FALLING_ATOM(mover)
- update_icon()
-
// override to make sure nothing is hidden
/turf/simulated/open/levelupdate()
for(var/obj/O in src)
diff --git a/code/modules/shieldgen/shield_gen.dm b/code/modules/shieldgen/shield_gen.dm
index eb3fe5fd1a1..a6b6bfa634a 100644
--- a/code/modules/shieldgen/shield_gen.dm
+++ b/code/modules/shieldgen/shield_gen.dm
@@ -43,7 +43,7 @@
field.Remove(D)
D.loc = null
return ..()
-
+
/obj/machinery/shield_gen/emag_act(var/remaining_charges, var/mob/user)
if(prob(75))
src.locked = !src.locked
@@ -237,25 +237,9 @@
//TODO MAKE THIS MULTIZ COMPATIBLE
//grab the border tiles in a circle around this machine
/obj/machinery/shield_gen/proc/get_shielded_turfs()
- var/list/out = list()
-
var/turf/gen_turf = get_turf(src)
+
if (!gen_turf)
return
- var/turf/T
- for (var/x_offset = -field_radius; x_offset <= field_radius; x_offset++)
- T = locate(gen_turf.x + x_offset, gen_turf.y - field_radius, gen_turf.z)
- if (T) out += T
-
- T = locate(gen_turf.x + x_offset, gen_turf.y + field_radius, gen_turf.z)
- if (T) out += T
-
- for (var/y_offset = -field_radius+1; y_offset < field_radius; y_offset++)
- T = locate(gen_turf.x - field_radius, gen_turf.y + y_offset, gen_turf.z)
- if (T) out += T
-
- T = locate(gen_turf.x + field_radius, gen_turf.y + y_offset, gen_turf.z)
- if (T) out += T
-
- return out
+ . = RANGE_TURFS(field_radius, gen_turf)
diff --git a/code/modules/shieldgen/shield_gen_external.dm b/code/modules/shieldgen/shield_gen_external.dm
index 182339d64e5..4264b8579b7 100644
--- a/code/modules/shieldgen/shield_gen_external.dm
+++ b/code/modules/shieldgen/shield_gen_external.dm
@@ -11,17 +11,27 @@
//Search for space turfs within range that are adjacent to a simulated turf.
/obj/machinery/shield_gen/external/get_shielded_turfs()
var/list/out = list()
-
+
var/turf/gen_turf = get_turf(src)
if (!gen_turf)
return
-
+
+ var/turf/U
var/turf/T
- for (var/x_offset = -field_radius; x_offset <= field_radius; x_offset++)
- for (var/y_offset = -field_radius; y_offset <= field_radius; y_offset++)
- T = locate(gen_turf.x + x_offset, gen_turf.y + y_offset, gen_turf.z)
- if (istype(T, /turf/space))
- //check neighbors of T
- if (locate(/turf/simulated/) in orange(1, T))
- out += T
+
+ for (var/tt in RANGE_TURFS(field_radius, gen_turf))
+ T = tt
+ // Ignore station areas.
+ if (the_station_areas[T.loc])
+ continue
+ else if (istype(T, /turf/space) || istype(T, /turf/simulated/floor/asteroid) || isopenturf(T))
+ for (var/uu in RANGE_TURFS(1, T))
+ U = uu
+ if (T == U)
+ continue
+
+ if (the_station_areas[U.loc])
+ out += U
+ break
+
return out