diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index a68646224cf..7e0941d3e65 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -103,6 +103,7 @@
#define PASSBLOB 8
#define PASSMOB 16
#define LETPASSTHROW 32
+#define PASSFENCE 64
//turf-only flags
#define NOJAUNT 1
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d8dd3bb44c8..e46086d9b2b 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1153,3 +1153,15 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
if(degrees)
appearance_flags |= PIXEL_SCALE
transform = M
+
+/*
+ Setter for the `density` variable.
+ Arguments:
+ * new_value - the new density you would want it to set.
+ Returns: Either null if identical to existing density, or the new density if different.
+*/
+/atom/proc/set_density(new_value)
+ if(density == new_value)
+ return
+ . = density
+ density = new_value
diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm
index c736026bca8..adf6fa59869 100644
--- a/code/game/objects/items/stacks/rods.dm
+++ b/code/game/objects/items/stacks/rods.dm
@@ -1,6 +1,17 @@
GLOBAL_LIST_INIT(rod_recipes, list ( \
- new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \
+ null,
+ new /datum/stack_recipe("railing", /obj/structure/railing, 3, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("railing corner", /obj/structure/railing/corner, 3, time = 10, one_per_turf = 1, on_floor = 1), \
+ null,
+ new /datum/stack_recipe_list("chainlink fence", list( \
+ new /datum/stack_recipe("chainlink fence", /obj/structure/fence, 5, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("chainlink fence post", /obj/structure/fence/post, 5, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("chainlink fence corner", /obj/structure/fence/corner, 5, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("chainlink fence door", /obj/structure/fence/door, 10, time = 10, one_per_turf = 1, on_floor = 1), \
+ new /datum/stack_recipe("chainlink fence end", /obj/structure/fence/end, 3, time = 10, one_per_turf = 1, on_floor = 1), \
+ )), \
))
/obj/item/stack/rods
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 4d58c90a095..6d1b884177d 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -59,30 +59,31 @@
/obj/structure/proc/do_climb(mob/living/user)
if(!can_touch(user) || !climbable)
- return
+ return FALSE
var/blocking_object = density_check()
if(blocking_object)
to_chat(user, "You cannot climb [src], as it is blocked by \a [blocking_object]!")
- return
+ return FALSE
var/turf/T = src.loc
- if(!T || !istype(T)) return
+ if(!T || !istype(T)) return FALSE
usr.visible_message("[user] starts climbing onto \the [src]!")
climber = user
if(!do_after(user, 50, target = src))
climber = null
- return
+ return FALSE
if(!can_touch(user) || !climbable)
climber = null
- return
+ return FALSE
usr.loc = get_turf(src)
if(get_turf(user) == get_turf(src))
usr.visible_message("[user] climbs onto \the [src]!")
climber = null
+ return TRUE
/obj/structure/proc/structure_shaken()
diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm
new file mode 100644
index 00000000000..21ad71f329f
--- /dev/null
+++ b/code/game/objects/structures/fence.dm
@@ -0,0 +1,216 @@
+//Chain link fences
+//Sprites ported from /VG/
+
+#define CUT_TIME 100
+#define CLIMB_TIME 150
+
+#define NO_HOLE 0 //section is intact
+#define MEDIUM_HOLE 1 //medium hole in the section - can climb through
+#define LARGE_HOLE 2 //large hole in the section - can walk through
+#define MAX_HOLE_SIZE LARGE_HOLE
+#define HOLE_REPAIR (hole_size * 2) //How many rods to fix these sections
+
+/obj/structure/fence
+ name = "fence"
+ desc = "A chain link fence. Not as effective as a wall, but generally it keeps people out."
+ density = TRUE
+ anchored = TRUE
+
+ icon = 'icons/obj/fence.dmi'
+ icon_state = "straight"
+
+ var/cuttable = TRUE
+ var/hole_size = NO_HOLE
+ var/invulnerable = FALSE
+ var/shock_cooldown = FALSE
+
+/obj/structure/fence/Initialize()
+ . = ..()
+ update_cut_status()
+
+/obj/structure/fence/examine(mob/user)
+ . = ..()
+ switch(hole_size)
+ if(MEDIUM_HOLE)
+ . += "There is a large hole in \the [src]."
+ if(LARGE_HOLE)
+ . += "\The [src] has been completely cut through."
+
+/obj/structure/fence/end
+ icon_state = "end"
+ cuttable = FALSE
+
+/obj/structure/fence/corner
+ icon_state = "corner"
+ cuttable = FALSE
+
+/obj/structure/fence/post
+ icon_state = "post"
+ cuttable = FALSE
+
+/obj/structure/fence/cut/medium
+ icon_state = "straight_cut2"
+ hole_size = MEDIUM_HOLE
+ climbable = TRUE
+
+/obj/structure/fence/cut/large
+ icon_state = "straight_cut3"
+ hole_size = LARGE_HOLE
+
+/obj/structure/fence/CanPass(atom/movable/mover, turf/target)
+ if(istype(mover) && mover.checkpass(PASSFENCE))
+ return TRUE
+ if(istype(mover, /obj/item/projectile))
+ return TRUE
+ if(!density)
+ return TRUE
+ return FALSE
+
+/*
+ Shock user with probability prb (if all connections & power are working)
+ Returns TRUE if shocked, FALSE otherwise
+ Totally not stolen from code\game\objects\structures\grille.dm
+*/
+/obj/structure/fence/proc/shock(mob/user, prb)
+ if(!prob(prb))
+ return FALSE
+ if(!in_range(src, user)) //To prevent TK and mech users from getting shocked
+ return FALSE
+ var/turf/T = get_turf(src)
+ var/obj/structure/cable/C = T.get_cable_node()
+ if(C)
+ if(electrocute_mob(user, C, src, 1, TRUE))
+ do_sparks(3, 1, src)
+ return TRUE
+ return FALSE
+
+/obj/structure/fence/wirecutter_act(mob/living/user, obj/item/W)
+ . = TRUE
+ if(shock(user, 100))
+ return
+ if(!cuttable)
+ to_chat(user, "This section of the fence can't be cut!")
+ return
+ if(invulnerable)
+ to_chat(user, "This fence is too strong to cut through!")
+ return
+ var/current_stage = hole_size
+ user.visible_message("\The [user] starts cutting through \the [src] with \the [W].",\
+ "You start cutting through \the [src] with \the [W].")
+ if(W.use_tool(src, user, CUT_TIME * W.toolspeed, volume = W.tool_volume))
+ if(current_stage == hole_size)
+ switch(hole_size)
+ if(NO_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.")
+ hole_size = MEDIUM_HOLE
+ if(MEDIUM_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.")
+ hole_size = LARGE_HOLE
+ if(LARGE_HOLE)
+ visible_message("\The [user] completely dismantles \the [src].")
+ to_chat(user, "You completely take apart \the [src].")
+ qdel(src)
+ return
+ update_cut_status()
+
+/obj/structure/fence/attackby(obj/item/C, mob/user)
+ if(shock(user, 90))
+ return
+ if(istype(C, /obj/item/stack/rods))
+ if(hole_size == NO_HOLE)
+ return
+ var/obj/item/stack/rods/R = C
+ if(R.get_amount() < HOLE_REPAIR)
+ to_chat(user, "You need [HOLE_REPAIR] rods to fix this fence!")
+ return
+ to_chat(user, "You begin repairing the fence...")
+ if(do_after(user, 3 SECONDS * C.toolspeed, target = src) && hole_size != NO_HOLE && R.use(HOLE_REPAIR))
+ playsound(src, C.usesound, 80, 1)
+ hole_size = NO_HOLE
+ obj_integrity = max_integrity
+ to_chat(user, "You repair the fence.")
+ update_cut_status()
+ return
+ . = ..()
+
+/obj/structure/fence/Bumped(atom/user)
+ if(!ismob(user))
+ return
+ if(shock_cooldown)
+ return
+ shock(user, 70)
+ shock_cooldown = TRUE // We do not want bump shock spam!
+ addtimer(CALLBACK(src, .proc/shock_cooldown), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
+
+/obj/structure/fence/proc/shock_cooldown()
+ shock_cooldown = FALSE
+
+/obj/structure/fence/attack_animal(mob/user)
+ . = ..()
+ if(. && !QDELETED(src) && !shock(user, 70))
+ take_damage(rand(5,10), BRUTE, "melee", 1)
+
+/obj/structure/fence/proc/update_cut_status()
+ if(!cuttable)
+ return
+ var/new_density = TRUE
+ switch(hole_size)
+ if(NO_HOLE)
+ icon_state = initial(icon_state)
+ climbable = FALSE
+ if(MEDIUM_HOLE)
+ icon_state = "straight_cut2"
+ climbable = TRUE
+ if(LARGE_HOLE)
+ icon_state = "straight_cut3"
+ new_density = FALSE
+ climbable = FALSE
+ set_density(new_density)
+
+//FENCE DOORS
+
+/obj/structure/fence/door
+ name = "fence door"
+ desc = "Not very useful without a real lock."
+ icon_state = "door_closed"
+ cuttable = FALSE
+ var/open = FALSE
+
+/obj/structure/fence/door/Initialize()
+ . = ..()
+ update_door_status()
+
+/obj/structure/fence/door/opened
+ icon_state = "door_opened"
+ open = TRUE
+ density = TRUE
+
+/obj/structure/fence/door/attack_hand(mob/user, list/modifiers)
+ shock(user, 70)
+ if(can_open(user))
+ toggle(user)
+ return TRUE
+
+/obj/structure/fence/door/proc/toggle(mob/user)
+ open = !open
+ visible_message("\The [user] [open ? "opens" : "closes"] \the [src].")
+ update_door_status()
+ playsound(src, 'sound/machines/door_open.ogg', 100, TRUE)
+
+/obj/structure/fence/door/proc/update_door_status()
+ set_density(!open)
+ icon_state = open ? "door_opened" : "door_closed"
+
+/obj/structure/fence/door/proc/can_open(mob/user)
+ return TRUE
+
+#undef CUT_TIME
+#undef CLIMB_TIME
+
+#undef NO_HOLE
+#undef MEDIUM_HOLE
+#undef LARGE_HOLE
+#undef MAX_HOLE_SIZE
+#undef HOLE_REPAIR
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
new file mode 100644
index 00000000000..2dc7a509206
--- /dev/null
+++ b/code/game/objects/structures/railings.dm
@@ -0,0 +1,136 @@
+/obj/structure/railing
+ name = "railing"
+ desc = "Basic railing meant to protect idiots like you from falling."
+ icon = 'icons/obj/fence.dmi'
+ icon_state = "railing"
+ density = TRUE
+ anchored = TRUE
+ pass_flags = LETPASSTHROW
+ climbable = TRUE
+ layer = ABOVE_MOB_LAYER
+ var/currently_climbed = FALSE
+
+/obj/structure/railing/corner //aesthetic corner sharp edges hurt oof ouch
+ icon_state = "railing_corner"
+ density = FALSE
+ climbable = FALSE
+
+/obj/structure/railing/attackby(obj/item/I, mob/living/user, params)
+ ..()
+ add_fingerprint(user)
+
+/obj/structure/railing/welder_act(mob/living/user, obj/item/I)
+ if(user.intent != INTENT_HELP)
+ return
+ if(obj_integrity >= max_integrity)
+ to_chat(user, "[src] is already in good condition!")
+ return
+ if(!I.tool_start_check(user, amount = 0))
+ return
+ to_chat(user, "You begin repairing [src]...")
+ if(I.use_tool(src, user, 40, volume = 50))
+ obj_integrity = max_integrity
+ to_chat(user, "You repair [src].")
+
+/obj/structure/railing/wirecutter_act(mob/living/user, obj/item/I)
+ if(anchored)
+ return
+ to_chat(user, "You cut apart the railing.")
+ I.play_tool_sound(src, 100)
+ deconstruct()
+ return TRUE
+
+/obj/structure/railing/deconstruct(disassembled)
+ if(!(flags & NODECONSTRUCT))
+ var/obj/item/stack/rods/rod = new /obj/item/stack/rods(drop_location(), 3)
+ transfer_fingerprints_to(rod)
+ return ..()
+
+///Implements behaviour that makes it possible to unanchor the railing.
+/obj/structure/railing/wrench_act(mob/living/user, obj/item/I)
+ if(flags & NODECONSTRUCT)
+ return
+ to_chat(user, "You begin to [anchored ? "unfasten the railing from":"fasten the railing to"] the floor...")
+ if(I.use_tool(src, user, volume = 75, extra_checks = CALLBACK(src, .proc/check_anchored, anchored)))
+ anchored = !anchored
+ to_chat(user, "You [anchored ? "fasten the railing to":"unfasten the railing from"] the floor.")
+ return TRUE
+
+/obj/structure/railing/corner/CanPass()
+ return TRUE
+
+/obj/structure/railing/corner/CheckExit()
+ return TRUE
+
+/obj/structure/railing/CanPass(atom/movable/mover, turf/target)
+ if(istype(mover) && mover.checkpass(PASSFENCE))
+ return TRUE
+ if(istype(mover, /obj/item/projectile))
+ return TRUE
+ if(ismob(mover))
+ var/mob/M = mover
+ if(M.flying)
+ return TRUE
+ if(mover.throwing)
+ return TRUE
+ if(get_dir(loc, target) != dir)
+ return density
+ return FALSE
+
+/obj/structure/railing/CheckExit(atom/movable/O, target)
+ var/mob/living/M = O
+ if(istype(O) && O.checkpass(PASSFENCE))
+ return TRUE
+ if(istype(O, /obj/item/projectile))
+ return TRUE
+ if(ismob(O))
+ if(M.flying || M.floating)
+ return TRUE
+ if(O.throwing)
+ return TRUE
+ if(O.move_force >= MOVE_FORCE_EXTREMELY_STRONG)
+ return TRUE
+ if(currently_climbed)
+ return TRUE
+ if(get_dir(O.loc, target) == dir)
+ return FALSE
+ return TRUE
+
+/obj/structure/railing/do_climb(mob/living/user)
+ var/initial_mob_loc = get_turf(user)
+ . = ..()
+ if(.)
+ currently_climbed = TRUE
+ if(initial_mob_loc != get_turf(src)) // If we are on the railing, we want to move in the same dir as the railing. Otherwise we get put on the railing
+ currently_climbed = FALSE
+ return
+ user.Move(get_step(user, dir), TRUE)
+ currently_climbed = FALSE
+
+/obj/structure/railing/proc/can_be_rotated(mob/user)
+ if(anchored)
+ to_chat(user, "[src] cannot be rotated while it is fastened to the floor!")
+ return FALSE
+
+ var/target_dir = turn(dir, -90)
+
+ if(!valid_window_location(loc, target_dir)) //Expanded to include rails, as well!
+ to_chat(user, "[src] cannot be rotated in that direction!")
+ return FALSE
+ return TRUE
+
+/obj/structure/railing/proc/check_anchored(checked_anchored)
+ if(anchored == checked_anchored)
+ return TRUE
+
+/obj/structure/railing/proc/after_rotation(mob/user)
+ add_fingerprint(user)
+
+/obj/structure/railing/AltClick(mob/user)
+ if(user.incapacitated())
+ to_chat(user, "You can't do that right now!")
+ return
+ if(!Adjacent(user))
+ return
+ if(can_be_rotated(user))
+ setDir(turn(dir, 90))
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index d1efa862a63..cd83952fcae 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -215,7 +215,7 @@
var/life_cycles = 0
var/life_cap = 20
anchored = TRUE
- pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSFENCE
/obj/effect/beam/i_beam/proc/hit()
diff --git a/code/modules/projectiles/guns/medbeam.dm b/code/modules/projectiles/guns/medbeam.dm
index 452023946ae..459c6681aa2 100644
--- a/code/modules/projectiles/guns/medbeam.dm
+++ b/code/modules/projectiles/guns/medbeam.dm
@@ -81,7 +81,7 @@
if(!istype(user_turf))
return 0
var/obj/dummy = new(user_turf)
- dummy.pass_flags |= PASSTABLE & PASSGLASS & PASSGRILLE //Grille/Glass so it can be used through common windows
+ dummy.pass_flags |= PASSTABLE & PASSGLASS & PASSGRILLE & PASSFENCE //Grille/Glass so it can be used through common windows
for(var/turf/turf in getline(user_turf,target))
if(turf.density)
qdel(dummy)
diff --git a/icons/obj/fence.dmi b/icons/obj/fence.dmi
new file mode 100644
index 00000000000..132482b4874
Binary files /dev/null and b/icons/obj/fence.dmi differ
diff --git a/paradise.dme b/paradise.dme
index cfe41674e94..7c5da49fd52 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1059,6 +1059,7 @@
#include "code\game\objects\structures\engicart.dm"
#include "code\game\objects\structures\extinguisher.dm"
#include "code\game\objects\structures\false_walls.dm"
+#include "code\game\objects\structures\fence.dm"
#include "code\game\objects\structures\flora.dm"
#include "code\game\objects\structures\fluff.dm"
#include "code\game\objects\structures\foodcart.dm"
@@ -1079,6 +1080,7 @@
#include "code\game\objects\structures\morgue.dm"
#include "code\game\objects\structures\noticeboard.dm"
#include "code\game\objects\structures\plasticflaps.dm"
+#include "code\game\objects\structures\railings.dm"
#include "code\game\objects\structures\reflector.dm"
#include "code\game\objects\structures\safe.dm"
#include "code\game\objects\structures\signs.dm"