diff --git a/code/game/objects/structures/railing.dm b/code/game/objects/structures/railing.dm
new file mode 100644
index 0000000000..7f9a6a98d3
--- /dev/null
+++ b/code/game/objects/structures/railing.dm
@@ -0,0 +1,318 @@
+// Based on railing.dmi from https://github.com/Endless-Horizon/CEV-Eris
+/obj/structure/railing
+ name = "railing"
+ desc = "A standard steel railing. Play stupid games win stupid prizes."
+ icon = 'icons/obj/railing.dmi'
+ density = 1
+ throwpass = 1
+ climbable = 1
+ layer = 3.2 //Just above doors
+ anchored = 1
+ flags = ON_BORDER
+ icon_state = "railing0"
+ var/broken = FALSE
+ var/health = 70
+ var/maxhealth = 70
+ var/check = 0
+
+/obj/structure/railing/New(loc, constructed = 0)
+ ..()
+ // TODO - "constructed" is not passed to us. We need to find a way to do this safely.
+ if (constructed) // player-constructed railings
+ anchored = 0
+ if(climbable)
+ verbs += /obj/structure/proc/climb_on
+
+/obj/structure/railing/initialize()
+ ..()
+ if(src.anchored)
+ update_icon(0)
+
+/obj/structure/railing/Destroy()
+ for(var/obj/structure/railing/R in oview(src, 1))
+ R.update_icon()
+ ..()
+
+/obj/structure/railing/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
+ if(!mover)
+ return 1
+ if(istype(mover) && mover.checkpass(PASSTABLE))
+ return 1
+ if(get_dir(loc, target) == dir)
+ return !density
+ else
+ return 1
+
+/obj/structure/railing/examine(mob/user)
+ . = ..()
+ if(health < maxhealth)
+ switch(health / maxhealth)
+ if(0.0 to 0.5)
+ to_chat(user, "It looks severely damaged!")
+ if(0.25 to 0.5)
+ to_chat(user, "It looks damaged!")
+ if(0.5 to 1.0)
+ to_chat(user, "It has a few scrapes and dents.")
+
+/obj/structure/railing/proc/take_damage(amount)
+ health -= amount
+ if(health <= 0)
+ visible_message("\The [src] breaks down!")
+ playsound(loc, 'sound/effects/grillehit.ogg', 50, 1)
+ new /obj/item/stack/rods(get_turf(usr))
+ qdel(src)
+
+/obj/structure/railing/proc/NeighborsCheck(var/UpdateNeighbors = 1)
+ check = 0
+ //if (!anchored) return
+ var/Rturn = turn(src.dir, -90)
+ var/Lturn = turn(src.dir, 90)
+
+ for(var/obj/structure/railing/R in src.loc)
+ if ((R.dir == Lturn) && R.anchored)
+ check |= 32
+ if (UpdateNeighbors)
+ R.update_icon(0)
+ if ((R.dir == Rturn) && R.anchored)
+ check |= 2
+ if (UpdateNeighbors)
+ R.update_icon(0)
+
+ for (var/obj/structure/railing/R in get_step(src, Lturn))
+ if ((R.dir == src.dir) && R.anchored)
+ check |= 16
+ if (UpdateNeighbors)
+ R.update_icon(0)
+ for (var/obj/structure/railing/R in get_step(src, Rturn))
+ if ((R.dir == src.dir) && R.anchored)
+ check |= 1
+ if (UpdateNeighbors)
+ R.update_icon(0)
+
+ for (var/obj/structure/railing/R in get_step(src, (Lturn + src.dir)))
+ if ((R.dir == Rturn) && R.anchored)
+ check |= 64
+ if (UpdateNeighbors)
+ R.update_icon(0)
+ for (var/obj/structure/railing/R in get_step(src, (Rturn + src.dir)))
+ if ((R.dir == Lturn) && R.anchored)
+ check |= 4
+ if (UpdateNeighbors)
+ R.update_icon(0)
+
+/obj/structure/railing/update_icon(var/UpdateNeighgors = 1)
+ NeighborsCheck(UpdateNeighgors)
+ layer = (dir == SOUTH) ? FLY_LAYER : initial(layer)
+ overlays.Cut()
+ if (!check || !anchored)//|| !anchored
+ icon_state = "railing0"
+ else
+ icon_state = "railing1"
+ if (check & 32)
+ overlays += image ('icons/obj/railing.dmi', src, "corneroverlay")
+ if ((check & 16) || !(check & 32) || (check & 64))
+ overlays += image ('icons/obj/railing.dmi', src, "frontoverlay_l")
+ if (!(check & 2) || (check & 1) || (check & 4))
+ overlays += image ('icons/obj/railing.dmi', src, "frontoverlay_r")
+ if(check & 4)
+ switch (src.dir)
+ if (NORTH)
+ overlays += image ('icons/obj/railing.dmi', src, "mcorneroverlay", pixel_x = 32)
+ if (SOUTH)
+ overlays += image ('icons/obj/railing.dmi', src, "mcorneroverlay", pixel_x = -32)
+ if (EAST)
+ overlays += image ('icons/obj/railing.dmi', src, "mcorneroverlay", pixel_y = -32)
+ if (WEST)
+ overlays += image ('icons/obj/railing.dmi', src, "mcorneroverlay", pixel_y = 32)
+
+/obj/structure/railing/verb/rotate()
+ set name = "Rotate Railing Counter-Clockwise"
+ set category = "Object"
+ set src in oview(1)
+
+ if(usr.incapacitated())
+ return 0
+
+ if(anchored)
+ to_chat(usr, "It is fastened to the floor therefore you can't rotate it!")
+ return 0
+
+ set_dir(turn(dir, 90))
+ update_icon()
+ return
+
+/obj/structure/railing/verb/revrotate()
+ set name = "Rotate Railing Clockwise"
+ set category = "Object"
+ set src in oview(1)
+
+ if(usr.incapacitated())
+ return 0
+
+ if(anchored)
+ to_chat(usr, "It is fastened to the floor therefore you can't rotate it!")
+ return 0
+
+ set_dir(turn(dir, -90))
+ update_icon()
+ return
+
+/obj/structure/railing/verb/flip() // This will help push railing to remote places, such as open space turfs
+ set name = "Flip Railing"
+ set category = "Object"
+ set src in oview(1)
+
+ if(usr.incapacitated())
+ return 0
+
+ if(anchored)
+ to_chat(usr, "It is fastened to the floor therefore you can't flip it!")
+ return 0
+
+ var/obj/occupied = neighbor_turf_impassable()
+ if(occupied)
+ to_chat(usr, "You can't flip \the [src] because there's \a [occupied] in the way.")
+ return 0
+
+ src.loc = get_step(src, src.dir)
+ set_dir(turn(dir, 180))
+ update_icon()
+ return
+
+/obj/structure/railing/CheckExit(atom/movable/O as mob|obj, target as turf)
+ if(istype(O) && O.checkpass(PASSTABLE))
+ return 1
+ if(get_dir(O.loc, target) == dir)
+ return 0
+ return 1
+
+/obj/structure/railing/attackby(obj/item/W as obj, mob/user as mob)
+ // Dismantle
+ if(istype(W, /obj/item/weapon/wrench) && !anchored)
+ playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
+ if(do_after(user, 20, src))
+ user.visible_message("\The [user] dismantles \the [src].", "You dismantle \the [src].")
+ new /obj/item/stack/material/steel(get_turf(usr), 2)
+ qdel(src)
+ return
+
+ // Repair
+ if(health < maxhealth && istype(W, /obj/item/weapon/weldingtool))
+ var/obj/item/weapon/weldingtool/F = W
+ if(F.welding)
+ playsound(src.loc, 'sound/items/Welder.ogg', 50, 1)
+ if(do_after(user, 20, src))
+ user.visible_message("\The [user] repairs some damage to \the [src].", "You repair some damage to \the [src].")
+ health = min(health+(maxhealth/5), maxhealth) // 20% repair per application
+ return
+
+ // Install
+ if(istype(W, /obj/item/weapon/screwdriver))
+ user.visible_message(anchored ? "\The [user] begins unscrewing \the [src]." : "\The [user] begins fasten \the [src]." )
+ playsound(loc, 'sound/items/Screwdriver.ogg', 75, 1)
+ if(do_after(user, 10, src))
+ to_chat(user, (anchored ? "You have unfastened \the [src] from the floor." : "You have fastened \the [src] to the floor."))
+ anchored = !anchored
+ update_icon()
+ return
+
+ // Handle harm intent grabbing/tabling.
+ if(istype(W, /obj/item/weapon/grab) && get_dist(src,user)<2)
+ var/obj/item/weapon/grab/G = W
+ if (istype(G.affecting, /mob/living))
+ var/mob/living/M = G.affecting
+ var/obj/occupied = turf_is_crowded()
+ if(occupied)
+ to_chat(user, "There's \a [occupied] in the way.")
+ return
+ if (G.state < 2)
+ if(user.a_intent == I_HURT)
+ if (prob(15)) M.Weaken(5)
+ M.apply_damage(8,def_zone = "head")
+ take_damage(8)
+ visible_message("[G.assailant] slams [G.affecting]'s face against \the [src]!")
+ playsound(loc, 'sound/effects/grillehit.ogg', 50, 1)
+ else
+ to_chat(user, "You need a better grip to do that!")
+ return
+ else
+ if (get_turf(G.affecting) == get_turf(src))
+ G.affecting.forceMove(get_step(src, src.dir))
+ else
+ G.affecting.forceMove(get_turf(src))
+ G.affecting.Weaken(5)
+ visible_message("[G.assailant] throws [G.affecting] over \the [src]!")
+ qdel(W)
+ return
+
+ else
+ playsound(loc, 'sound/effects/grillehit.ogg', 50, 1)
+ take_damage(W.force)
+
+ return ..()
+
+/obj/structure/railing/ex_act(severity)
+ switch(severity)
+ if(1.0)
+ qdel(src)
+ return
+ if(2.0)
+ qdel(src)
+ return
+ if(3.0)
+ qdel(src)
+ return
+ else
+ return
+
+// Duplicated from structures.dm, but its a bit different.
+/obj/structure/railing/do_climb(var/mob/living/user)
+ if(!can_climb(user))
+ return
+
+ usr.visible_message("[user] starts climbing onto \the [src]!")
+ climbers |= user
+
+ if(!do_after(user,(issmall(user) ? 20 : 34)))
+ climbers -= user
+ return
+
+ if(!can_climb(user, post_climb_check=1))
+ climbers -= user
+ return
+
+ if(get_turf(user) == get_turf(src))
+ usr.forceMove(get_step(src, src.dir))
+ else
+ usr.forceMove(get_turf(src))
+
+ usr.visible_message("[user] climbed over \the [src]!")
+ if(!anchored) take_damage(maxhealth) // Fatboy
+ climbers -= user
+
+/obj/structure/railing/can_climb(var/mob/living/user, post_climb_check=0)
+ if(!..())
+ return 0
+
+ // Normal can_climb() handles climbing from adjacent turf onto our turf. But railings also allow climbing
+ // from our turf onto an adjacent! If that is the case we need to do checks for that too...
+ if(get_turf(user) == get_turf(src))
+ var/obj/occupied = neighbor_turf_impassable()
+ if(occupied)
+ to_chat(user, "You can't climb there, there's \a [occupied] in the way.")
+ return 0
+ return 1
+
+// TODO - This here might require some investigation
+/obj/structure/proc/neighbor_turf_impassable()
+ var/turf/T = get_step(src, src.dir)
+ if(!T || !istype(T))
+ return 0
+ if(T.density == 1)
+ return T
+ for(var/obj/O in T.contents)
+ if(istype(O,/obj/structure))
+ var/obj/structure/S = O
+ if(S.climbable) continue
+ if(O && O.density && !(O.flags & ON_BORDER && !(turn(O.dir, 180) & dir)))
+ return O
diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm
index 11f6021c75..ae72c7a9ec 100644
--- a/code/modules/materials/material_recipes.dm
+++ b/code/modules/materials/material_recipes.dm
@@ -54,6 +54,7 @@
recipes += new/datum/stack_recipe("fire extinguisher cabinet frame", /obj/item/frame/extinguisher_cabinet, 4, time = 5, one_per_turf = 0, on_floor = 1)
//recipes += new/datum/stack_recipe("fire axe cabinet frame", /obj/item/frame/fireaxe_cabinet, 4, time = 5, one_per_turf = 0, on_floor = 1)
recipes += new/datum/stack_recipe("wall girders", /obj/structure/girder, 2, time = 50, one_per_turf = 1, on_floor = 1)
+ recipes += new/datum/stack_recipe("railing", /obj/structure/railing, 2, time = 50, one_per_turf = 0, on_floor = 1)
recipes += new/datum/stack_recipe("turret frame", /obj/machinery/porta_turret_construct, 5, time = 25, one_per_turf = 1, on_floor = 1)
recipes += new/datum/stack_recipe_list("airlock assemblies", list( \
new/datum/stack_recipe("standard airlock assembly", /obj/structure/door_assembly, 4, time = 50, one_per_turf = 1, on_floor = 1), \
diff --git a/icons/obj/railing.dmi b/icons/obj/railing.dmi
new file mode 100644
index 0000000000..c37c89c159
Binary files /dev/null and b/icons/obj/railing.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index 21b4fe41af..11721aa4e2 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -1003,6 +1003,7 @@
#include "code\game\objects\structures\morgue_vr.dm"
#include "code\game\objects\structures\musician.dm"
#include "code\game\objects\structures\noticeboard.dm"
+#include "code\game\objects\structures\railing.dm"
#include "code\game\objects\structures\safe.dm"
#include "code\game\objects\structures\signs.dm"
#include "code\game\objects\structures\simple_doors.dm"