diff --git a/beestation.dme b/beestation.dme
index 9c0e244b937c7..b7ca356d20019 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -3719,6 +3719,7 @@
#include "monkestation\code\datums\votesounds.dm"
#include "monkestation\code\datums\announcers\duke_announcer.dm"
#include "monkestation\code\datums\brain_damage\mild.dm"
+#include "monkestation\code\datums\components\chain.dm"
#include "monkestation\code\datums\components\hot_ice.dm"
#include "monkestation\code\datums\components\pricetag.dm"
#include "monkestation\code\datums\components\strong_pull.dm"
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index 911e2d2057c32..5b34fe1f5859e 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -512,6 +512,10 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restrai
full_w_class = WEIGHT_CLASS_SMALL
grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil
usesound = 'sound/items/deconstruct.ogg'
+ //MONKESTATION EDIT: The target when tethering two things together
+ var/atom/movable/tether_target
+ //MONKESTATION EDIT: A temporary tether before they attach the vehicle to the second thing
+ var/datum/component/chain/temp_tether
/obj/item/stack/cable_coil/cyborg
is_cyborg = 1
@@ -589,6 +593,46 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restrai
amount += extra
update_icon()
+//MONKESTATION EDIT: Tethering people to vehicles
+/obj/item/stack/cable_coil/afterattack(atom/movable/target, mob/user, proximity_flag)
+ if(!proximity_flag)
+ return
+
+ if(!istype(target))
+ return ..()
+
+ if(QDELETED(temp_tether))
+ tether_target = null
+ temp_tether = null
+
+ if(target == tether_target)
+ to_chat(user,"You untie the cable from [target].")
+ qdel(temp_tether)
+ tether_target = null
+ temp_tether = null
+ return
+
+ if(!tether_target)
+ if(istype(target, /obj/vehicle))
+ to_chat(user,"You tie one end of the cable around [target]")
+ tether_target = target
+ temp_tether = target.AddComponent(/datum/component/chain, user, 4)
+ return
+
+ user.visible_message("[user] begins tying the other end of the cable [target]","You begin tying the other end of the cable [target]")
+ if(do_after(user, 8 SECONDS, target=target))
+ target.AddComponent(/datum/component/chain, tether_target, 4)
+ use(1)
+ qdel(temp_tether)
+ temp_tether = null
+ tether_target = null
+
+/obj/item/stack/cable_coil/dropped(mob/user)
+ . = ..()
+ if(temp_tether)
+ qdel(temp_tether)
+ temp_tether = null
+ tether_target = null
///////////////////////////////////////////////
diff --git a/monkestation/code/datums/components/chain.dm b/monkestation/code/datums/components/chain.dm
new file mode 100644
index 0000000000000..1403643b7671a
--- /dev/null
+++ b/monkestation/code/datums/components/chain.dm
@@ -0,0 +1,124 @@
+
+/*
+This component attaches to things that'll follow another object
+Like connecting a person to a wheelchair so they get dragged behind it
+*/
+/datum/component/chain
+ //The thing the parent will follow
+ var/atom/movable/attachment_point
+
+ var/atom/movable/owner
+ var/max_dist
+ var/equal_force
+
+ var/datum/beam/tether_beam
+
+/datum/component/chain/Initialize(target, max_dist=3, equal_force = TRUE, draw_beam = TRUE)
+ if(!ismovableatom(parent) || !ismovableatom(target))
+ return COMPONENT_INCOMPATIBLE
+
+ owner = parent
+ attachment_point = target
+ src.max_dist = max_dist
+ src.equal_force = equal_force
+
+ if(draw_beam)
+ tether_beam = owner.Beam(target, "usb_cable_beam", 'icons/obj/wiremod.dmi', maxdistance = max_dist+1)
+
+ RegisterSignal(target, COMSIG_MOVABLE_PRE_MOVE, .proc/on_attachment_move)
+ RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, .proc/on_parent_move)
+ RegisterSignal(owner, COMSIG_ATOM_ATTACK_HAND, .proc/on_parent_touched)
+ RegisterSignal(owner, COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), .proc/on_parent_wirecutters)
+
+ if(draw_beam)
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/on_atom_moved)
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/on_atom_moved)
+
+/datum/component/chain/Destroy(force, silent)
+ . = ..()
+ if(tether_beam)
+ qdel(tether_beam)
+ tether_beam = null
+ owner = null
+ attachment_point = null
+
+/datum/component/chain/proc/on_atom_moved(atom/movable/mover, old_loc, movement_dir, forced, old_locs, momentum_change)
+ SIGNAL_HANDLER
+
+ spawn(0)
+ tether_beam.recalculate()
+
+/datum/component/chain/proc/on_attachment_move(atom/movable/mover, newloc)
+ SIGNAL_HANDLER
+
+ if(!owner)
+ qdel(src)
+ return
+
+ var/dist = get_dist(owner,newloc)
+ if(dist < max_dist)
+ return
+
+ if(!owner.anchored)
+ owner.Move(get_step_towards(owner,mover))
+
+ if(isliving(owner))
+ var/mob/living/living_owner = owner
+ living_owner.Knockdown(1 SECONDS)
+
+ if(dist > max_dist)
+ owner.visible_message("The tether snaps!")
+ qdel(src)
+ return
+
+/datum/component/chain/proc/on_parent_move(atom/movable/mover, newloc)
+ SIGNAL_HANDLER
+
+ if(!attachment_point)
+ qdel(src)
+ return
+
+ var/dist = get_dist(attachment_point,newloc)
+ if(dist < max_dist)
+ return
+
+ if(dist > max_dist)
+ qdel(src)
+ return
+
+ if(attachment_point.anchored)
+ return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
+ attachment_point.Move(get_step_towards(attachment_point,mover))
+
+ if(equal_force)
+ return
+
+ if(isliving(owner))
+ var/mob/living/living_owner = owner
+ living_owner.Knockdown(1 SECONDS)
+ to_chat(owner,"You trip on the tether!")
+ return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
+
+/datum/component/chain/proc/on_parent_touched(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, .proc/untie_parent, user)
+
+/datum/component/chain/proc/untie_parent(mob/user)
+ if(user == owner)
+ user.visible_message("[user] begins untying the cable from \himself.","You begin untying the cable from yourself.")
+ else
+ user.visible_message("[user] begins untying the cable from [owner].","You begin untying the cable from [owner].")
+ if(do_mob(user, owner, 10 SECONDS))
+ qdel(src)
+
+/datum/component/chain/proc/on_parent_wirecutters(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(user == owner)
+ user.visible_message("[user] cuts \himself free.","You cut yourself free.")
+ else
+ user.visible_message("[user] cuts [owner] free.","[user] cuts you free] free.")
+ qdel(src)
+ return COMPONENT_BLOCK_TOOL_ATTACK