diff --git a/mojave/icons/obstacles/turnstile.dmi b/mojave/icons/obstacles/turnstile.dmi
new file mode 100644
index 00000000000..550f9457a7d
Binary files /dev/null and b/mojave/icons/obstacles/turnstile.dmi differ
diff --git a/mojave/structures/obstacles.dm b/mojave/structures/obstacles.dm
index ef739de1aa8..718a9041f27 100644
--- a/mojave/structures/obstacles.dm
+++ b/mojave/structures/obstacles.dm
@@ -1058,3 +1058,107 @@
 	var/turf/my_turf = get_turf(loc)
 	if(my_turf)
 		REMOVE_TRAIT(my_turf, TRAIT_REMOVE_SLOWDOWN, STAIRS_ON_TURF)
+
+// Turnstiles //
+
+/**
+ * Turnstiles. They block movement in two directions (the two walls of the turnstile) and knock down people coming from another. Dir+180 is the intended entry direction.
+ * NOTE: we only have sprites for NORTH and SOUTH turnstiles.
+ *
+*/
+/obj/structure/ms13/turnstile
+	name = "turnstile"
+	desc = "You can only go one way... assuming you don't hop over."
+	icon = 'mojave/icons/obstacles/turnstile.dmi'
+	icon_state = "tourniquet"
+	density = TRUE
+	anchored = TRUE
+	max_integrity = 400
+	integrity_failure = 0.85
+
+/obj/structure/ms13/turnstile/Initialize()
+	. = ..()
+	AddElement(/datum/element/climbable, climb_time = 1 SECONDS, climb_stun = 0, no_stun = TRUE, jump_over = TRUE)
+
+	var/static/list/loc_connections = list(
+		COMSIG_ATOM_EXIT = .proc/on_exit,
+		COMSIG_ATOM_ENTERED = .proc/on_entered,
+	)
+	AddElement(/datum/element/connect_loc, loc_connections)
+
+/// Stop people from just walking out the side
+/obj/structure/ms13/turnstile/proc/on_exit(datum/source, atom/movable/leaving, direction)
+	SIGNAL_HANDLER
+
+	if(leaving == src)
+		return // Let's not block ourselves.
+
+	if((direction & dir) || (direction & turn(dir, 180)))
+		return
+
+	if (leaving.throwing)
+		return
+
+	if (leaving.movement_type & (PHASING | FLYING | FLOATING))
+		return
+
+	leaving.Bump(src)
+	return COMPONENT_ATOM_BLOCK_EXIT
+
+/// Handles flicking the rotation animation.
+/obj/structure/ms13/turnstile/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+	SIGNAL_HANDLER
+
+	if(arrived.throwing)
+		return
+
+	if(arrived.movement_type & (PHASING | FLYING | FLOATING))
+		return
+
+	// ghetto check for if we're being climbed over (which sets our density to FALSE during the step)
+	if(!density)
+		return
+
+	if(!broken)
+		flick("rotating", src)
+
+/obj/structure/ms13/turnstile/CanAllowThrough(atom/movable/mover, border_dir)
+	. = ..()
+
+	if(istype(mover, /obj/projectile))
+		return
+
+	if(istype(mover, /obj/item))
+		var/obj/item/I = mover
+		if(I.w_class <= WEIGHT_CLASS_NORMAL)
+			return TRUE
+
+	if(ismob(mover))
+		if((border_dir == turn(dir, 90)) || (border_dir == turn(dir, -90)))
+			return
+
+	if((border_dir & turn(dir, 180)) && !broken)
+		if(mover.move_force >= MOVE_FORCE_STRONG)
+			// 20 damage at FORCE_STRONG, +10 for every step above that
+			take_damage(10 * mover.move_force / MOVE_FORCE_DEFAULT)
+
+		// taking damage could have broken the bars
+		if(!broken)
+			return
+
+	return TRUE
+
+/obj/structure/ms13/turnstile/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
+	if ((to_dir == turn(dir, 90)) || (to_dir == turn(dir, -90)))
+		return FALSE
+	return ..()
+
+/obj/structure/ms13/turnstile/atom_break()
+	. = ..()
+	broken = TRUE
+	icon_state = "bent"
+
+/obj/structure/ms13/turnstile/atom_fix()
+	. = ..()
+	broken = FALSE
+	icon_state = "tourniquet"