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"