diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm
new file mode 100644
index 00000000000..d3ba2def7e7
--- /dev/null
+++ b/code/datums/elements/climbable.dm
@@ -0,0 +1,104 @@
+/datum/element/climbable
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 2
+ ///Time it takes to climb onto the object
+ var/climb_time = (2 SECONDS)
+ ///Stun duration for when you get onto the object
+ var/climb_stun = (2 SECONDS)
+ ///Assoc list of object being climbed on - climbers. This allows us to check who needs to be shoved off a climbable object when its clicked on.
+ var/list/current_climbers
+
+/datum/element/climbable/Attach(datum/target, climb_time, climb_stun)
+ . = ..()
+
+ if(!isatom(target) || isarea(target))
+ return ELEMENT_INCOMPATIBLE
+ if(climb_time)
+ src.climb_time = climb_time
+ if(climb_stun)
+ src.climb_stun = climb_stun
+
+ RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, .proc/attack_hand)
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+ RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive)
+ RegisterSignal(target, COMSIG_ATOM_BUMPED, .proc/try_speedrun)
+
+/datum/element/climbable/Detach(datum/target)
+ UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
+ return ..()
+
+/datum/element/climbable/proc/on_examine(atom/source, mob/user, list/examine_texts)
+ SIGNAL_HANDLER
+
+ if(can_climb(source, user))
+ examine_texts += "[source] looks climbable"
+
+/datum/element/climbable/proc/can_climb(atom/source, mob/user)
+ return TRUE
+
+/datum/element/climbable/proc/attack_hand(atom/climbed_thing, mob/user)
+ SIGNAL_HANDLER
+ var/list/climbers = LAZYACCESS(current_climbers, climbed_thing)
+ for(var/i in climbers)
+ var/mob/living/structure_climber = i
+ if(structure_climber == user)
+ return
+ user.changeNext_move(CLICK_CD_MELEE)
+ user.do_attack_animation(climbed_thing)
+ structure_climber.Paralyze(40)
+ structure_climber.visible_message("[structure_climber] is knocked off [climbed_thing].", "You're knocked off [climbed_thing]!", "You see [structure_climber] get knocked off [climbed_thing].")
+
+
+/datum/element/climbable/proc/climb_structure(atom/climbed_thing, mob/living/user)
+ if(!can_climb(climbed_thing, user))
+ return
+ climbed_thing.add_fingerprint(user)
+ user.visible_message("[user] starts climbing onto [climbed_thing].", \
+ "You start climbing onto [climbed_thing]...")
+ var/adjusted_climb_time = climb_time
+ if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //climbing takes twice as long without help from the hands.
+ adjusted_climb_time *= 2
+ if(isalien(user))
+ adjusted_climb_time *= 0.25 //aliens are terrifyingly fast
+ if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am???
+ adjusted_climb_time *= 0.8
+ LAZYADDASSOC(current_climbers, climbed_thing, user)
+ if(do_after(user, adjusted_climb_time, climbed_thing))
+ if(QDELETED(climbed_thing)) //Checking if structure has been destroyed
+ return
+ if(do_climb(climbed_thing, user))
+ user.visible_message("[user] climbs onto [climbed_thing].", \
+ "You climb onto [climbed_thing].")
+ log_combat(user, climbed_thing, "climbed onto")
+ if(climb_stun)
+ user.Stun(climb_stun)
+ else
+ to_chat(user, "You fail to climb onto [climbed_thing].")
+ LAZYREMOVEASSOC(current_climbers, climbed_thing, user)
+
+
+/datum/element/climbable/proc/do_climb(atom/climbed_thing, mob/living/user)
+ climbed_thing.density = FALSE
+ . = step(user, get_dir(user,climbed_thing.loc))
+ climbed_thing.density = TRUE
+
+///Handles climbing onto the atom when you click-drag
+/datum/element/climbable/proc/mousedrop_receive(atom/climbed_thing, atom/movable/dropped_atom, mob/user)
+ SIGNAL_HANDLER
+ if(user == dropped_atom && isliving(dropped_atom))
+ var/mob/living/living_target = dropped_atom
+ if(isanimal(living_target))
+ var/mob/living/simple_animal/animal = dropped_atom
+ if (!animal.dextrous)
+ return
+ if(living_target.mobility_flags & MOBILITY_MOVE)
+ INVOKE_ASYNC(src, .proc/climb_structure, climbed_thing, living_target)
+ return
+
+///Tries to climb onto the target if the forced movement of the mob allows it
+/datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee)
+ SIGNAL_HANDLER
+ if(!istype(bumpee))
+ return
+ if(bumpee.force_moving?.allow_climbing)
+ do_climb(source, bumpee)
diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm
index 2252bb6c665..14aa43364ce 100644
--- a/code/datums/forced_movement.dm
+++ b/code/datums/forced_movement.dm
@@ -84,10 +84,3 @@
. = TryMove(TRUE)
. = . && (vic.loc != tar.loc)
-
-/mob/Bump(atom/A)
- . = ..()
- if(force_moving?.allow_climbing && isstructure(A))
- var/obj/structure/S = A
- if(S.climbable)
- S.do_climb(src)
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index 99d64b00294..fe6f0cb6d20 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -106,11 +106,14 @@
proj_pass_rate = 20
pass_flags_self = LETPASSTHROW
bar_material = SAND
- climbable = TRUE
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = list(SMOOTH_GROUP_SANDBAGS)
canSmoothWith = list(SMOOTH_GROUP_SANDBAGS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_SECURITY_BARRICADE)
+/obj/structure/barricade/sandbags/Initialize()
+ . = ..()
+ AddElement(/datum/element/climbable)
+
/obj/structure/barricade/security
name = "security barrier"
desc = "A deployable barrier. Provides good cover in fire fights."
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 08d3fda3597..f5ed1357ba6 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -8,13 +8,7 @@
flags_ricochet = RICOCHET_HARD
receive_ricochet_chance_mod = 0.6
pass_flags_self = PASSSTRUCTURE
-
- var/climb_time = 20
- var/climb_stun = 20
- var/climbable = FALSE
- var/mob/living/structureclimber
- var/broken = 0 //similar to machinery's stat BROKEN
-
+ var/broken = FALSE
/obj/structure/Initialize()
if (!armor)
@@ -33,78 +27,10 @@
QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
-/obj/structure/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- if(structureclimber && structureclimber != user)
- user.changeNext_move(CLICK_CD_MELEE)
- user.do_attack_animation(src)
- structureclimber.Paralyze(40)
- structureclimber.visible_message("[structureclimber] is knocked off [src].", "You're knocked off [src]!", "You see [structureclimber] get knocked off [src].")
-
/obj/structure/ui_act(action, params)
add_fingerprint(usr)
return ..()
-/obj/structure/MouseDrop_T(atom/movable/O, mob/user)
- . = ..()
- if(!climbable)
- return
- if(user == O && isliving(O))
- var/mob/living/L = O
- if(isanimal(L))
- var/mob/living/simple_animal/A = L
- if (!A.dextrous)
- return
- if(L.mobility_flags & MOBILITY_MOVE)
- climb_structure(user)
- return
- if(!istype(O, /obj/item) || user.get_active_held_item() != O)
- return
- if(iscyborg(user))
- return
- if(!user.dropItemToGround(O))
- return
- if (O.loc != src.loc)
- step(O, get_dir(O, src))
-
-/obj/structure/proc/do_climb(atom/movable/A)
- if(climbable)
- if(A.loc == src.loc)
- var/turf/where_to_climb = get_step(A,dir)
- if(!where_to_climb.is_blocked_turf())
- A.forceMove(where_to_climb)
- return TRUE
- density = FALSE
- . = step(A,get_dir(A,src.loc))
- density = TRUE
-
-/obj/structure/proc/climb_structure(mob/living/user)
- src.add_fingerprint(user)
- user.visible_message("[user] starts climbing onto [src].", \
- "You start climbing onto [src]...")
- var/adjusted_climb_time = climb_time
- if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //climbing takes twice as long without help from the hands.
- adjusted_climb_time *= 2
- if(isalien(user))
- adjusted_climb_time *= 0.25 //aliens are terrifyingly fast
- if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am???
- adjusted_climb_time *= 0.8
- structureclimber = user
- if(do_mob(user, user, adjusted_climb_time))
- if(src.loc) //Checking if structure has been destroyed
- if(do_climb(user))
- user.visible_message("[user] climbs onto [src].", \
- "You climb onto [src].")
- log_combat(user, src, "climbed onto")
- if(climb_stun)
- user.Stun(climb_stun)
- . = 1
- else
- to_chat(user, "You fail to climb onto [src].")
- structureclimber = null
-
/obj/structure/examine(mob/user)
. = ..()
if(!(resistance_flags & INDESTRUCTIBLE))
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index e4d60c849a4..df26a03e182 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -167,11 +167,15 @@
opened = TRUE
if(!dense_when_open)
density = FALSE
- climb_time *= 0.5 //it's faster to climb onto an open thing
dump_contents()
update_icon()
+ after_open(user, force)
return TRUE
+///Proc to override for effects after opening a door
+/obj/structure/closet/proc/after_open(mob/living/user, force = FALSE)
+ return
+
/obj/structure/closet/proc/insert(atom/movable/AM)
if(contents.len >= storage_capacity)
return -1
@@ -218,12 +222,17 @@
return FALSE
take_contents()
playsound(loc, close_sound, close_sound_volume, TRUE, -3)
- climb_time = initial(climb_time)
opened = FALSE
density = TRUE
update_icon()
+ after_close(user)
return TRUE
+///Proc to override for effects after closing a door
+/obj/structure/closet/proc/after_close(mob/living/user)
+ return
+
+
/obj/structure/closet/proc/toggle(mob/living/user)
if(opened)
return close(user)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 4c92ce90129..8b14ced6df2 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -9,15 +9,13 @@
allow_objects = TRUE
allow_dense = TRUE
dense_when_open = TRUE
- climbable = TRUE
- climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy
- climb_stun = 0 //climbing onto crates isn't hard, guys
delivery_icon = "deliverycrate"
open_sound = 'sound/machines/crate_open.ogg'
close_sound = 'sound/machines/crate_close.ogg'
open_sound_volume = 35
close_sound_volume = 50
drag_slowdown = 0
+ var/climb_time = 20
var/obj/item/paper/fluff/jobs/cargo/manifest/manifest
/obj/structure/closet/crate/Initialize()
@@ -25,6 +23,7 @@
if(icon_state == "[initial(icon_state)]open")
opened = TRUE
update_icon()
+ AddElement(/datum/element/climbable, climb_time = opened ? climb_time : climb_time * 0.5, climb_stun = 0)
/obj/structure/closet/crate/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
@@ -51,6 +50,17 @@
if(manifest)
tear_manifest(user)
+/obj/structure/closet/crate/after_open(mob/living/user, force)
+ . = ..()
+ RemoveElement(/datum/element/climbable)
+ AddElement(/datum/element/climbable, climb_time = climb_time * 0.5, climb_stun = 0)
+
+/obj/structure/closet/crate/after_close(mob/living/user)
+ . = ..()
+ RemoveElement(/datum/element/climbable)
+ AddElement(/datum/element/climbable, climb_time = climb_time, climb_stun = 0)
+
+
/obj/structure/closet/crate/open(mob/living/user, force = FALSE)
. = ..()
if(. && manifest)
diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm
index 8697662ed09..87ba54103d8 100644
--- a/code/game/objects/structures/fence.dm
+++ b/code/game/objects/structures/fence.dm
@@ -79,11 +79,11 @@
if(MEDIUM_HOLE)
visible_message("\The [user] cuts into \the [src] some more.")
to_chat(user, "You could probably fit yourself through that hole now. Although climbing through would be much faster if you made it even bigger.")
- climbable = TRUE
+ AddElement(/datum/element/climbable)
if(LARGE_HOLE)
visible_message("\The [user] completely cuts through \the [src].")
to_chat(user, "The hole in \the [src] is now big enough to walk through.")
- climbable = FALSE
+ RemoveElement(/datum/element/climbable)
update_cut_status()
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index 00ed93933aa..b67e5f07a2c 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -5,7 +5,10 @@
icon_state = "railing"
density = TRUE
anchored = TRUE
- climbable = TRUE
+
+ var/climbable = TRUE
+ ///Initial direction of the railing.
+ var/ini_dir
/obj/structure/railing/corner //aesthetic corner sharp edges hurt oof ouch
icon_state = "railing_corner"
@@ -16,6 +19,13 @@
. = ..()
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, .proc/can_be_rotated),CALLBACK(src,.proc/after_rotation))
+
+/obj/structure/railing/Initialize()
+ . = ..()
+ ini_dir = dir
+ if(climbable)
+ AddElement(/datum/element/climbable)
+
/obj/structure/railing/attackby(obj/item/I, mob/living/user, params)
..()
add_fingerprint(user)
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 2e60dcaf8cd..34358041ad4 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -22,7 +22,6 @@
anchored = TRUE
pass_flags_self = PASSTABLE | LETPASSTHROW
layer = TABLE_LAYER
- climbable = TRUE
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
var/buildstack = /obj/item/stack/sheet/metal
@@ -37,6 +36,12 @@
smoothing_groups = list(SMOOTH_GROUP_TABLES)
canSmoothWith = list(SMOOTH_GROUP_TABLES)
+/obj/structure/table/Initialize(mapload, _buildstack)
+ . = ..()
+ if(_buildstack)
+ buildstack = _buildstack
+ AddElement(/datum/element/climbable)
+
/obj/structure/table/examine(mob/user)
. = ..()
. += deconstruction_hints(user)
diff --git a/code/game/objects/structures/transit_tubes/transit_tube.dm b/code/game/objects/structures/transit_tubes/transit_tube.dm
index 223d690c2bc..e3a8e2e008a 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube.dm
@@ -7,7 +7,6 @@
density = TRUE
layer = LOW_ITEM_LAYER
anchored = TRUE
- climbable = TRUE
pass_flags_self = PASSGLASS
var/tube_construction = /obj/structure/c_transit_tube
var/list/tube_dirs //list of directions this tube section can connect to.
@@ -20,6 +19,7 @@
setDir(newdirection)
init_tube_dirs()
generate_tube_overlays()
+ AddElement(/datum/element/climbable)
/obj/structure/transit_tube/Destroy()
for(var/obj/structure/transit_tube_pod/P in loc)
diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm
index 061d4285ec4..38e1bf0e58d 100644
--- a/code/modules/religion/religion_structures.dm
+++ b/code/modules/religion/religion_structures.dm
@@ -6,7 +6,6 @@
density = TRUE
anchored = TRUE
layer = TABLE_LAYER
- climbable = TRUE
pass_flags_self = LETPASSTHROW
can_buckle = TRUE
buckle_lying = 90 //we turn to you!
@@ -16,6 +15,7 @@
/obj/structure/altar_of_gods/Initialize(mapload)
. = ..()
reflect_sect_in_icons()
+ AddElement(/datum/element/climbable)
/obj/structure/altar_of_gods/ComponentInitialize()
. = ..()
diff --git a/tgstation.dme b/tgstation.dme
index 9487526362c..1f076211243 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -594,6 +594,7 @@
#include "code\datums\elements\bsa_blocker.dm"
#include "code\datums\elements\caltrop.dm"
#include "code\datums\elements\cleaning.dm"
+#include "code\datums\elements\climbable.dm"
#include "code\datums\elements\decal.dm"
#include "code\datums\elements\digitalcamo.dm"
#include "code\datums\elements\dryable.dm"