diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index f67a00faf56..f5b7f5bd10f 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -38,7 +38,7 @@
for(var/atom/movable/AM as mob|obj in src)
Entered(AM)
-
+
turfs += src
if(dynamic_lighting)
diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm
index 16c89569fc0..a3c2471011e 100644
--- a/code/modules/clothing/spacesuits/rig/modules/utility.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm
@@ -14,6 +14,8 @@
* /obj/item/rig_module/device/paperdispenser
* /obj/item/rig_module/device/pen
* /obj/item/rig_module/device/stamp
+ * /obj/item/rig_module/actuators
+ * /obj/item/rig_module/actuators/combat
*/
/obj/item/rig_module/device
@@ -472,3 +474,143 @@
interface_desc = "Eats trash like no one's business."
device_type = /obj/item/weapon/matter_decompiler
+
+/obj/item/rig_module/actuators
+ name = "leg actuators"
+ desc = "A set of electromechanical actuators, for safe travesal of multilevelled areas."
+ icon_state = "generic"
+ interface_name = "leg actuators"
+ interface_desc = "Allows you to fall from heights and to jump up onto ledges."
+
+ disruptive = 1
+
+ use_power_cost = 5
+ module_cooldown = 25
+
+ /*
+ * TOGGLE - dampens fall, on or off.
+ * SELECTABLE - Jump forward or up!
+ */
+ toggleable = 1
+ selectable = 1
+ usable = 0
+
+ engage_string = "Toggle Leg Actuators"
+ activate_string = "Enable Leg Actuators"
+ deactivate_string = "Disable Leg Actuators"
+
+ var/combatType = 0 // Determines whether or not the actuators can do special combat oriented tasks.
+ // Such as leaping faster, or grappling targets.
+ var/leapDistance = 4 // Determines how far the actuators allow you to leap (radius, inclusive).
+
+/obj/item/rig_module/actuators/combat
+ name = "military grade leg actuators"
+ desc = "A set of high-powered hydraulic actuators, for improved traversal of multilevelled areas."
+ interface_name = "combat leg actuators"
+
+ combatType = 1
+ leapDistance = 7
+
+ use_power_cost = 10
+
+/obj/item/rig_module/actuators/engage(var/atom/target)
+ if (!..())
+ return 0
+
+ // This is for when you toggle it on or off. Why do they both run the same
+ // proc chain ...? :l
+ if (!target)
+ return 1
+
+ var/mob/living/carbon/human/H = holder.wearer
+
+ if (!isturf(H.loc))
+ to_chat(H, "You cannot leap out of your current location!")
+ return 0
+
+ var/turf/T = get_turf(target)
+
+ if (!T || T.density)
+ to_chat(H, "You cannot leap at solid walls!")
+ return 0
+
+ // Saved, we need it more than 1 place.
+ var/dist = max(get_dist(T, get_turf(H)), 0)
+
+ if (dist)
+ for (var/A in T)
+ var/atom/aa = A
+ if (combatType && ismob(aa))
+ continue
+
+ if (aa.density)
+ to_chat(H, "You cannot leap at a location with solid objects on it!")
+ return 0
+
+ if (T.z != H.z || dist > leapDistance)
+ to_chat(H, "You cannot leap at such a distant object!")
+ return 0
+
+ // Handle leaping at targets with a combat capable version here.
+ if (combatType && dist && (ismob(target) || (locate(/mob/living) in T)))
+ H.leap(target, leapDistance)
+ return 1
+
+ // If dist -> horizontal leap. Otherwise, the user clicked the turf that they're
+ // currently on. Which means they want to do a vertical leap upwards!
+ if (dist)
+ H.visible_message("\The [H] leaps horizontally at \the [T]!",
+ "You leap horizontally at \the [T]!",
+ "You hear an electric whirr followed by a weighty thump!")
+ H.face_atom(T)
+ H.throw_at(T, leapDistance, 1, src)
+ return 1
+ else
+ var/turf/simulated/open/TA = GetAbove(src)
+ if (!istype(TA))
+ to_chat(H, "There is a ceiling above you that stop you from leaping upwards!")
+ return 0
+
+ for (var/atom/A in TA)
+ if (!A.CanPass(src, TA, 1.5, 0))
+ to_chat(H, "\The [A] blocks you!")
+ return 0
+
+ var/turf/leapEnd = get_step(TA, H.dir)
+ if (!leapEnd || istype(leapEnd, /turf/simulated/open) || istype(leapEnd, /turf/space)\
+ || leapEnd.density || leapEnd.contains_dense_objects())
+ to_chat(H, "There is no valid ledge to scale ahead of you!")
+ return 0
+
+ H.visible_message("\The [H] leaps up, out of view!",
+ "You leap up!")
+
+ // 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)
+
+ H.forceMove(TA)
+
+ // Combat type actuators are better, they allow you to jump instantly onto
+ // a ledge. Regular actuators make you have to climb the rest of the way.
+ if (!combatType)
+ H.visible_message("\The [H] starts pulling \himself up onto \the [leapEnd].",
+ "You start pulling yourself up onto \the [leapEnd].")
+ 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)
+ return 1
+
+ H.visible_message("\The [H] finishes climbing onto \the [leapEnd].",
+ "You finish climbing onto \the [leapEnd].")
+ else
+ 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)
+ H.forceMove(leapEnd)
+
+ return 1
diff --git a/code/modules/clothing/spacesuits/rig/suits/light.dm b/code/modules/clothing/spacesuits/rig/suits/light.dm
index 0795d09ec04..68b8a785e9c 100644
--- a/code/modules/clothing/spacesuits/rig/suits/light.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/light.dm
@@ -97,7 +97,8 @@
/obj/item/rig_module/ai_container,
/obj/item/rig_module/power_sink,
/obj/item/rig_module/datajack,
- /obj/item/rig_module/self_destruct
+ /obj/item/rig_module/self_destruct,
+ /obj/item/rig_module/actuators/combat
)
..()
diff --git a/code/modules/clothing/spacesuits/rig/suits/merc.dm b/code/modules/clothing/spacesuits/rig/suits/merc.dm
index d56db0c3f6d..211b783fc0d 100644
--- a/code/modules/clothing/spacesuits/rig/suits/merc.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/merc.dm
@@ -25,12 +25,14 @@
/obj/item/rig_module/power_sink,
/obj/item/rig_module/electrowarfare_suite,
/obj/item/rig_module/chem_dispenser/combat,
- /obj/item/rig_module/fabricator/energy_net
+ /obj/item/rig_module/fabricator/energy_net,
+ /obj/item/rig_module/actuators/combat
)
//Has most of the modules removed
/obj/item/weapon/rig/merc/empty
initial_modules = list(
/obj/item/rig_module/ai_container,
- /obj/item/rig_module/electrowarfare_suite //might as well
+ /obj/item/rig_module/electrowarfare_suite, //might as well
+ /obj/item/rig_module/actuators/combat // What the dude above me said.
)
diff --git a/code/modules/clothing/spacesuits/rig/suits/station.dm b/code/modules/clothing/spacesuits/rig/suits/station.dm
index 3b56e45540b..6f0222d30be 100644
--- a/code/modules/clothing/spacesuits/rig/suits/station.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/station.dm
@@ -81,7 +81,8 @@
/obj/item/rig_module/device/drill,
/obj/item/rig_module/device/orescanner,
/obj/item/rig_module/device/rcd,
- /obj/item/rig_module/vision/meson
+ /obj/item/rig_module/vision/meson,
+ /obj/item/rig_module/actuators
)
/obj/item/weapon/rig/industrial/syndicate
@@ -147,7 +148,8 @@
/obj/item/rig_module/maneuvering_jets,
/obj/item/rig_module/mounted/plasmacutter,
/obj/item/rig_module/device/rcd,
- /obj/item/rig_module/vision/meson
+ /obj/item/rig_module/vision/meson,
+ /obj/item/rig_module/actuators
)
/obj/item/clothing/gloves/rig/ce
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 10bcfd127a2..3bdc97c1498 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -49,7 +49,7 @@
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()
+/mob/living/carbon/human/proc/leap(var/mob/living/T = null, var/max_range = 4)
set category = "Abilities"
set name = "Leap"
set desc = "Leap at a target and grab them aggressively."
@@ -61,17 +61,18 @@
src << "You cannot leap in your current state."
return
- var/list/choices = list()
- for(var/mob/living/M in view(6,src))
- if(!istype(M,/mob/living/silicon))
- choices += M
- choices -= src
+ if (!T)
+ var/list/choices = list()
+ for(var/mob/living/M in view(6,src))
+ if(!istype(M,/mob/living/silicon))
+ choices += M
+ choices -= src
- var/mob/living/T = input(src,"Who do you wish to leap at?") as null|anything in choices
+ T = input(src,"Who do you wish to leap at?") as null|anything in choices
if(!T || !src || src.stat) return
- if(get_dist(get_turf(T), get_turf(src)) > 4) return
+ if(get_dist(get_turf(T), get_turf(src)) > max_range) return
if(last_special > world.time)
return
@@ -85,7 +86,10 @@
src.visible_message("\The [src] leaps at [T]!")
src.throw_at(get_step(get_turf(T),get_turf(src)), 4, 1, src)
- playsound(src.loc, 'sound/voice/shriek1.ogg', 50, 1)
+
+ // Only Vox get to shriek. Seriously.
+ if (isvox(src))
+ playsound(src.loc, 'sound/voice/shriek1.ogg', 50, 1)
sleep(5)
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index 95a4613bdbb..35ebccfbf86 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -189,8 +189,12 @@
if(locate(/obj/structure/lattice, dest) || locate(/obj/structure/stairs, dest))
return FALSE
+ // The var/climbers API is implemented here.
+ if (LAZYLEN(dest.climbers) && (src in dest.climbers))
+ return FALSE
+
// See if something prevents us from falling.
- for(var/atom/A in below)
+ for (var/atom/A in below)
if(!A.CanPass(src, dest))
return FALSE
@@ -212,6 +216,10 @@
// Only things that stop mechas are atoms that, well, stop them.
// Lattices and stairs get crushed in fall_through.
/obj/mecha/can_fall(turf/below, turf/simulated/open/dest = src.loc)
+ // The var/climbers API is implemented here.
+ if (LAZYLEN(dest.climbers) && (src in dest.climbers))
+ return FALSE
+
// See if something prevents us from falling.
for(var/atom/A in below)
if(!A.CanPass(src, dest))
diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm
index a47b00b51b7..d74bd86aaf9 100644
--- a/code/modules/multiz/turf.dm
+++ b/code/modules/multiz/turf.dm
@@ -42,6 +42,9 @@
var/tmp/depth
+ var/tmp/list/climbers // A lazy list to contain a list of mobs who are currently scaling
+ // up this turf. Used in human/can_fall.
+
// An override of turf/Enter() to make it so that magboots allow you to stop
// falling off the damned rock.
/turf/simulated/open/Enter(mob/living/carbon/human/mover, atom/oldloc)
@@ -66,6 +69,10 @@
above = null
below = null
+
+ LAZYCLEARLIST(climbers)
+ UNSETEMPTY(climbers)
+
return ..()
/turf/simulated/open/get_smooth_underlay_icon(image/underlay_appearance, turf/asking_turf, adjacency_dir)
diff --git a/html/changelogs/skull132-actuators.yml b/html/changelogs/skull132-actuators.yml
new file mode 100644
index 00000000000..d761b3da3ef
--- /dev/null
+++ b/html/changelogs/skull132-actuators.yml
@@ -0,0 +1,6 @@
+author: Skull132
+
+delete-after: True
+
+changes:
+ - rscadd: "Added the leg actuator RIG module. These allow you to fall from heights if enabled, without taking damage; to leap horizontally (Vox style); and to climb up open turfs if you're facing a solid turf above you. Combat versions also allow you to grapel people."