diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm index ea4d28411f2..00cc4aa5d03 100644 --- a/code/game/objects/items/weapons/traps.dm +++ b/code/game/objects/items/weapons/traps.dm @@ -5,22 +5,46 @@ throw_range = 1 gender = PLURAL icon = 'icons/obj/item/traps/traps.dmi' - var/icon_base = "beartrap" - icon_state = "beartrap0" + icon_state = "beartrap" randpixel = 0 center_of_mass = null throwforce = 0 w_class = ITEMSIZE_NORMAL origin_tech = list(TECH_ENGINEERING = 2) matter = list(DEFAULT_WALL_MATERIAL = 18750) + can_buckle = list(/mob/living) + + update_icon_on_init = TRUE + + /// This boolean indicates that the trap is deployed and ready to trap a mob. It can only be deployed after being secured. var/deployed = FALSE - var/time_to_escape = 60 + + /// This boolean determines whether the trap will unanchor itself after being disarmed + var/unsecure_on_disarm = TRUE + + /// Time in deciseconds needed to escape from the trap + var/time_to_escape = 1 MINUTE + + /// When the trap triggers and does damage, this will be the armor penetration value var/activated_armor_penetration = 0 /obj/item/trap/Initialize() . = ..() update_icon() +/obj/item/trap/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) + . = ..() + . += get_trap_examine_text(user, distance, is_adjacent, infix, suffix) + +/obj/item/trap/proc/get_trap_examine_text(mob/user, distance, is_adjacent, infix, suffix) + . = list() + if(buckled) + . += SPAN_NOTICE("You can free the creature inside the trap by clicking on it.") + else if(deployed) + . += SPAN_NOTICE("You can disarm the trap by clicking on it.") + else + . += SPAN_NOTICE("You can deploy the trap by using it in-hand.") + /obj/item/trap/proc/can_use(mob/user) return (user.IsAdvancedToolUser() && !issilicon(user) && !user.stat && !user.restrained()) @@ -32,38 +56,26 @@ anchored = TRUE /obj/item/trap/proc/deploy(mob/user) - user.visible_message( - SPAN_WARNING("\The [user] starts to deploy \the [src]."), - SPAN_WARNING("You begin deploying \the [src]!"), - SPAN_WARNING("You hear the slow creaking of a spring.") - ) + user.visible_message("[SPAN_BOLD(user)] starts deploying \the [src]...", SPAN_NOTICE("You begin deploying \the [src]!"), SPAN_WARNING("You hear the slow creaking of a spring.")) if(do_after(user, 5 SECONDS)) - user.visible_message( - SPAN_WARNING("\The [user] deploys \the [src]."), - SPAN_WARNING("You deploy \the [src]!"), - SPAN_WARNING("You hear a latch click loudly.") - ) - + user.visible_message("[SPAN_BOLD(user)] deploys \the [src].", SPAN_WARNING("You deploy \the [src]!"), SPAN_WARNING("You hear a latch click loudly.")) deployed = TRUE update_icon() return TRUE + return FALSE /obj/item/trap/user_unbuckle(mob/user) - if(buckled && can_use(user)) - user.visible_message( - SPAN_NOTICE("\The [user] begins freeing \the [buckled] from \the [src]..."), - SPAN_NOTICE("You carefully begin to free \the [buckled] from \the [src]..."), - SPAN_NOTICE("You hear metal creaking.") - ) - if(do_after(user, time_to_escape)) - user.visible_message( - SPAN_NOTICE("\The [user] frees \the [buckled] from \the [src]."), - SPAN_NOTICE("You free \the [buckled] from \the [src].") - ) - unbuckle() - anchored = FALSE + if(!buckled || !can_use(user)) + return + + user.visible_message("[SPAN_BOLD(user)] begins freeing \the [buckled] from \the [src]...", SPAN_NOTICE("You carefully begin to free \the [buckled] from \the [src]..."), SPAN_NOTICE("You hear metal creaking.")) + + if(do_after(user, time_to_escape)) + user.visible_message("[SPAN_BOLD(user)] frees \the [buckled] from \the [src].", SPAN_NOTICE("You free \the [buckled] from \the [src].")) + unbuckle() + anchored = FALSE /obj/item/trap/attack_hand(mob/user) if(can_use(user)) @@ -76,19 +88,16 @@ ..() /obj/item/trap/proc/disarm_trap(var/mob/user) - user.visible_message(SPAN_NOTICE("\The [user] starts to disarm \the [src]..."), SPAN_NOTICE("You begin disarming \the [src]..."), SPAN_WARNING("You hear a latch click followed by the slow creaking of a spring.")) + user.visible_message("[SPAN_BOLD(user)] starts disarming \the [src]...", SPAN_NOTICE("You begin disarming \the [src]..."), SPAN_WARNING("You hear a latch click followed by the slow creaking of a spring.")) if(do_after(user, 6 SECONDS)) - user.visible_message(SPAN_NOTICE("\The [user] disarms \the [src]!"), SPAN_NOTICE("You disarm \the [src]!")) + user.visible_message("[SPAN_BOLD(user)] disarms \the [src]!", SPAN_NOTICE("You disarm \the [src]!")) deployed = FALSE - anchored = FALSE + if(unsecure_on_disarm) + anchored = FALSE update_icon() /obj/item/trap/proc/attack_mob(mob/living/L) - var/target_zone - if(L.lying) - target_zone = ran_zone() - else - target_zone = pick(BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG) + var/target_zone = L.lying ? ran_zone() : pick(BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG) var/success = L.apply_damage(30, DAMAGE_BRUTE, target_zone, used_weapon = src, armor_pen = activated_armor_penetration) if(!success) @@ -105,9 +114,7 @@ if(did_trap) //trap the victim in place - can_buckle = list(/mob/living) buckle(L) - can_buckle = initial(can_buckle) deployed = FALSE to_chat(L, FONT_LARGE(SPAN_DANGER("The steel jaws of \the [src] bite into you, [did_trap ? "trapping you in place" : "cleaving your limb clean off"]!"))) @@ -136,7 +143,7 @@ shake_animation() /obj/item/trap/update_icon() - icon_state = "[icon_base][deployed]" + icon_state = "[initial(icon_state)][deployed]" /* @@ -155,530 +162,6 @@ desc_antag = "This device has an even higher chance of penetrating armor and locking foes in place." activated_armor_penetration = 100 -/** - * # Animal trap - * - * Used to catch small animals like rats, lizards, and chicks - */ -/obj/item/trap/animal - name = "small trap" - desc = "A small mechanical trap that's used to catch small animals like rats, lizards, and chicks." - icon_base = "small" - icon_state = "small0" - throwforce = 2 - force = 1 - w_class = ITEMSIZE_SMALL - origin_tech = list(TECH_ENGINEERING = 1) - matter = list(DEFAULT_WALL_MATERIAL = 1750) - deployed = FALSE - time_to_escape = 3 // Minutes - can_buckle = list(/mob/living) - health = 100 - can_astar_pass = CANASTARPASS_ALWAYS_PROC - - var/breakout = FALSE - var/last_shake = 0 - var/list/allowed_mobs = list(/mob/living/simple_animal/rat, /mob/living/simple_animal/chick, /mob/living/simple_animal/lizard) - var/release_time = 0 - var/list/resources = list(rods = 6) - var/spider = TRUE - var/datum/weakref/captured = null - -/obj/item/trap/animal/MouseDrop_T(atom/dropping, mob/user) - var/mob/living/M = dropping - if(!istype(M)) - return - - if(!captured) - if(!is_type_in_list(M, allowed_mobs)) - to_chat(user, SPAN_WARNING("[M] won't fit in there!")) - else if(do_after(user, 5 SECONDS)) - capture(M) - else - to_chat(user, SPAN_WARNING("\The [src] is already full!")) - -/obj/item/trap/animal/update_icon() - icon_state = "[icon_base][deployed]" - -/obj/item/trap/animal/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) - . = ..() - if(captured) - var/datum/L = captured.resolve() - if (L) - . += SPAN_WARNING("[L] is trapped inside!") - return - else if(deployed) - . += SPAN_WARNING("It's set up and ready to capture something.") - else - . += SPAN_NOTICE("\The [src] is empty and un-deployed.") - -/obj/item/trap/animal/Crossed(atom/movable/AM) - if(!deployed || !anchored) - return - - if(captured) // just in case but this shouldn't happen - return - - capture(AM) - -/obj/item/trap/animal/proc/capture(var/mob/AM, var/msg = 1) - if(isliving(AM) && is_type_in_list(AM, allowed_mobs)) - var/mob/living/L = AM - if(msg) - L.visible_message( - SPAN_DANGER("[L] enters \the [src], and it snaps shut with a clatter!"), - SPAN_DANGER("You enter \the [src], and it snaps shut with a clatter!"), - "You hear a loud metallic snap!" - ) - if(AM.loc != loc) - AM.forceMove(loc) - captured = WEAKREF(L) - INVOKE_ASYNC(src, PROC_REF(buckle), L) - layer = L.layer + 0.1 - playsound(src, 'sound/weapons/beartrap_shut.ogg', 100, 1) - deployed = FALSE - src.shake_animation() - update_icon() - -/obj/item/trap/animal/proc/req_breakout() - if(deployed || !captured) - return 0 // Trap-door is open, no one is captured. - if(breakout) - return -1 //Already breaking out. - return 1 - -/obj/item/trap/animal/proc/breakout_callback(var/mob/living/escapee) - if (QDELETED(escapee)) - return FALSE - - if ((world.time - last_shake) > 5 SECONDS) - playsound(loc, 'sound/effects/grillehit.ogg', 100, 1) - shake_animation() - last_shake = world.time - - return TRUE - -// If we are stuck, and need to get out -/obj/item/trap/animal/user_unbuckle(var/mob/living/escapee) - if (req_breakout() < 1) - return - - escapee.next_move = world.time + 100 - escapee.last_special = world.time + 100 - to_chat(escapee, SPAN_WARNING("You begin to shake and bump the lock of \the [src]. (this will take about [time_to_escape] minutes).")) - visible_message(SPAN_DANGER("\The [src] begins to shake violently! Something is attempting to escape it!")) - - var/time = 360 * time_to_escape * 2 - breakout = TRUE - - if (!do_after(escapee, time, src)) - breakout = FALSE - return - - breakout = FALSE - to_chat(escapee, SPAN_WARNING("You successfully break out!")) - visible_message(SPAN_DANGER("\The [escapee] successfully breaks out of \the [src]!")) - playsound(loc, 'sound/effects/grillehit.ogg', 100, 1) - - release() - -/obj/item/trap/animal/CollidedWith(atom/bumped_atom) - if(deployed && is_type_in_list(bumped_atom, allowed_mobs)) - Crossed(bumped_atom) - else - ..() - -/obj/item/trap/animal/verb/release_animal() - set src in orange(1) - set category = "Object" - set name = "Release animal" - - if(!usr.canmove || usr.stat || usr.restrained()) - return - - if(!ishuman(usr)) - to_chat(usr, SPAN_WARNING("This mob type can't use this verb.")) - return - - var/datum/M = captured ? captured.resolve() : null - - if(M) - var/open = alert("Do you want to open the cage and free \the [M]?",,"No","Yes") - - if(open == "No") - return - - if(!can_use(usr)) - to_chat(usr, SPAN_WARNING("You cannot use \the [src].")) - return - - if(usr == M) - to_chat(usr, SPAN_WARNING("You can't open \the [src] from the inside! You'll need to force it open.")) - return - - var/adj = src.Adjacent(usr) - if(!adj) - attack_self(src) - return - - release(usr) - -/obj/item/trap/animal/crush_act() - if(captured) - var/datum/L = captured ? captured.resolve() : null - if(L && isliving(L)) - var/mob/living/LL = L - LL.gib() - new /obj/item/stack/material/steel(get_turf(src)) - qdel(src) - -/obj/item/trap/animal/ex_act(severity) - switch(severity) - if(1) - health -= rand(120, 240) - if(2) - health -= rand(60, 120) - if(3) - health -= rand(30, 60) - - if (health <= 0) - if(captured) - release() - new /obj/item/stack/material/steel(get_turf(src)) - qdel(src) - -/obj/item/trap/animal/bullet_act(var/obj/item/projectile/Proj) - for (var/atom/movable/A in src) - if(istype(A, /mob/living)) - var/mob/living/M = A - M.bullet_act(Proj) - -/obj/item/trap/animal/proc/release(var/mob/user, var/turf/target) - if(!target) - target = src.loc - if(user) - visible_message(SPAN_NOTICE("[user] opens \the [src].")) - - var/datum/L = captured ? captured.resolve() : null - if (!L) - captured = null - release_time = world.time - return - - var/msg - if (isliving(L)) - var/mob/living/ll = L - msg = SPAN_WARNING("[ll] runs out of \the [src].") - - unbuckle() - captured = null - visible_message(msg) - shake_animation() - update_icon() - release_time = world.time - layer = initial(layer) - -/obj/item/trap/animal/attackby(obj/item/attacking_item, mob/user) - if(istype(attacking_item, /obj/item/grab)) - var/obj/item/grab/G = attacking_item - var/mob/living/M = G.affecting - - if (G.state == GRAB_PASSIVE || G.state == GRAB_UPGRADING) - to_chat(user, SPAN_NOTICE("You need a better grip on \the [M]!")) - return - - user.visible_message(SPAN_NOTICE("[user] starts putting [M] into \the [src]."), - SPAN_NOTICE("You start putting [M] into \the [src].")) - - if (!is_type_in_list(M, allowed_mobs)) - to_chat(user, SPAN_WARNING("[M] won't fit in there!")) - return - - if (do_mob(user, M, 3 SECONDS, needhand = 0)) - if(captured?.resolve()) - return - capture(M) - - else if(attacking_item.iswelder()) - var/obj/item/weldingtool/WT = attacking_item - if(!WT.isOn()) - to_chat(user, SPAN_WARNING("\The [WT] is off!")) - return - user.visible_message(SPAN_NOTICE("[user] is trying to slice \the [src] open!"), - SPAN_NOTICE("You are trying to slice \the [src] open!")) - - if(WT.use_tool(src, user, 60, volume = 50)) - if(WT.use(2, user)) - user.visible_message(SPAN_NOTICE("[user] slices \the [src] open!"), - SPAN_NOTICE("You slice \the [src] open!")) - new /obj/item/stack/rods(src.loc, resources["rods"]) - if(resources.len == 2) - new /obj/item/stack/material/steel(src.loc, resources["metal"]) - release(user) - qdel(src) - - else if(attacking_item.isscrewdriver()) - var/turf/T = get_turf(src) - if(!T) - to_chat(user, SPAN_WARNING("There is nothing to secure [src] to!")) - return - - user.visible_message(SPAN_NOTICE("[user] is trying to [anchored ? "un" : "" ]secure \the [src]!"), - SPAN_NOTICE("You are trying to [anchored ? "un" : "" ]secure \the [src]!")) - var/sound_to_play = pick(list('sound/items/Screwdriver.ogg', 'sound/items/Screwdriver2.ogg')) - playsound(src.loc, sound_to_play, 50, 1) - - if(attacking_item.use_tool(src, user, 30, volume = 50)) - density = !density - anchored = !anchored - user.visible_message(SPAN_NOTICE("[user] [anchored ? "" : "un" ]secures \the [src]!"), - SPAN_NOTICE("You [anchored ? "" : "un" ]secure \the [src]!")) - else - ..() - -/obj/item/trap/animal/Move() - ..() - if(captured) - var/datum/M = captured.resolve() - if(isliving(M)) - var/mob/living/L = M - if(L && buckled.buckled_to == src) - L.forceMove(loc) - else if(L) - captured = null - else - captured = null - -/obj/item/trap/animal/attack_hand(mob/user) - if(user.loc == src || captured) - return - - if(anchored && deployed) - to_chat(user, SPAN_NOTICE("\The [src] is already anchored and set!")) - else if(anchored) - deploy(user) - else - ..() - -/obj/item/trap/animal/proc/pass_without_trace(mob/user, pct = 100) - if(!is_type_in_list(user, allowed_mobs)) - user.forceMove(loc) - user.visible_message(SPAN_NOTICE("[user] passes over \the [src] without triggering it."), - SPAN_NOTICE("You pass over \the [src] without triggering it.") - ) - else - user.visible_message(SPAN_NOTICE("[user] attempts to pass through \the [src] without triggering it."), - SPAN_NOTICE("You attempt to pass through \the [src] without triggering it. ") - ) - if(do_after(user, 2 SECONDS, src)) - if(prob(pct)) - user.forceMove(loc) - user.visible_message(SPAN_NOTICE("[user] passes through \the [src] without triggering it."), - SPAN_NOTICE("You pass through \the [src] without triggering it.") - ) - else - user.forceMove(loc) - user.visible_message(SPAN_WARNING("[user] accidentally triggers \the [src]!"), - SPAN_WARNING("You accidentally trigger \the [src]!") - ) - capture(user) - -/obj/item/trap/animal/MouseDrop(over_object, src_location, over_location) - if(!isliving(usr) || !src.Adjacent(usr)) - return - - if(captured) - pass_without_trace(usr) // It's full - - if((usr.isMonkey() && (/mob/living/carbon/human/monkey in allowed_mobs)) || is_type_in_list(usr, allowed_mobs)) // Because monkeys can be of type of just human. - pass_without_trace(usr, 50) - return - - else if(iscarbon(usr)) - pass_without_trace(usr) - - else - ..() - -/obj/item/trap/animal/attack_self(mob/user) - if(!can_use(user)) - to_chat(user, SPAN_WARNING("You cannot use \the [src].")) - return - - if(captured) - release(user, user.loc) - -/obj/item/trap/animal/attack(var/target, mob/living/user) - if(!deployed) - return - - if(captured) // It is full - return - - if(isliving(target)) - var/mob/living/M = target - if(is_type_in_list(M, allowed_mobs)) - user.visible_message( - SPAN_WARNING("[user] traps [M] inside of \the [src]."), - SPAN_WARNING("You trap [M] inside of the \the [src]!"), - "You hear a loud metallic snap!" - ) - capture(M, msg = 0) - else - ..() - -/obj/item/trap/animal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) - return TRUE - -/obj/item/trap/animal/CanAStarPass(to_dir, datum/can_pass_info/pass_info) - return TRUE - - -/** - * # Animal trap (Medium) - * - * Used to catch medium animals like cats, monkeys, nymphs, and wayward maintenance drones - */ -/obj/item/trap/animal/medium - name = "medium trap" - desc = "A medium mechanical trap that is used to catch moderately-sized animals like cats, monkeys, nymphs, and wayward maintenance drones." - icon_base = "medium" - icon_state = "medium0" - throwforce = 4 - force = 11 - w_class = ITEMSIZE_LARGE - origin_tech = list(TECH_ENGINEERING = 3) - matter = list(DEFAULT_WALL_MATERIAL = 5750) - deployed = FALSE - resources = list(rods = 12) - spider = FALSE - -/obj/item/trap/animal/medium/Initialize() - . = ..() - allowed_mobs = list( - /mob/living/simple_animal/cat, /mob/living/simple_animal/corgi, /mob/living/simple_animal/hostile/retaliate/diyaab, /mob/living/carbon/human/monkey, /mob/living/simple_animal/penguin, /mob/living/simple_animal/crab, - /mob/living/simple_animal/chicken, /mob/living/simple_animal/yithian, /mob/living/carbon/alien/diona, /mob/living/silicon/robot/drone, /mob/living/silicon/pai, - /mob/living/simple_animal/spiderbot, /mob/living/simple_animal/hostile/tree) - -/** - * # Animal trap (Large) - * - * Used to catch larger animals, from spiders and dogs to bears and even larger mammals - */ -/obj/item/trap/animal/large - name = "large trap" - desc = "A large mechanical trap that is used to catch larger animals, from spiders and dogs to bears and even larger mammals." - icon_base = "large" - icon_state = "large0" - throwforce = 6 - force = 10 - w_class = 6 - density = 1 - origin_tech = list(TECH_ENGINEERING = 3) - matter = list(DEFAULT_WALL_MATERIAL = 15750) - deployed = FALSE - resources = list(rods = 12, metal = 4) - spider = FALSE - -/obj/item/trap/animal/large/Initialize() - . = ..() - allowed_mobs = list( - /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/cow, /mob/living/simple_animal/corgi/fox, - /mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/giant_spider, - /mob/living/simple_animal/hostile/commanded/dog, /mob/living/simple_animal/hostile/retaliate/cavern_dweller, /mob/living/carbon/human, - /mob/living/simple_animal/pig) - -/obj/item/trap/animal/large/attack_hand(mob/user) - if(user == buckled) - return - else if(!anchored) - to_chat(user, SPAN_WARNING("You need to anchor \the [src] first!")) - else if(captured) - to_chat(user, SPAN_WARNING("You can't deploy \the [src] with something caught!")) - else - ..() - -/obj/item/trap/animal/large/attackby(obj/item/attacking_item, mob/user) - if(attacking_item.iswrench()) - var/turf/T = get_turf(src) - if(!T) - to_chat(user, SPAN_WARNING("There is nothing to secure [src] to!")) - return - - if(anchored && deployed) - to_chat(user, SPAN_WARNING("You can't do that while \the [src] is deployed! Undeploy it first.")) - return - - user.visible_message(SPAN_NOTICE("[user] begins [anchored ? "un" : "" ]securing \the [src]!"), - SPAN_NOTICE("You begin [anchored ? "un" : "" ]securing \the [src]!")) - - if(attacking_item.use_tool(src, user, 30, volume = 50)) - anchored = !anchored - user.visible_message(SPAN_NOTICE("[user] [anchored ? "" : "un" ]secures \the [src]!"), - SPAN_NOTICE("You [anchored ? "" : "un" ]secure \the [src]!")) - - else if(attacking_item.isscrewdriver()) - // Unlike smaller traps, screwdriver shouldn't work on this. - return - else - ..() - -/obj/item/trap/animal/large/MouseDrop(over_object, src_location, over_location) - if(captured) - to_chat(usr, SPAN_WARNING("The trap door's down, you can't get through there!")) - return - - if(!src.Adjacent(usr)) - return - - if(!ishuman(usr)) - ..() - return - - var/pct = 0 - if(usr.a_intent == I_HELP) - pct = 100 - else if(usr.a_intent != I_HURT) - pct = 50 - - pass_without_trace(usr, pct) - -/obj/item/trap/animal/large/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) - if(deployed) - return TRUE - return FALSE - -/obj/item/large_trap_foundation - name = "large trap foundation" - desc = "A metal foundation for large trap, it is missing metals rods to hold the prey." - icon = 'icons/obj/item/traps/traps.dmi' - icon_state = "large_foundation" - throwforce = 4 - force = 11 - w_class = ITEMSIZE_HUGE - -/obj/item/large_trap_foundation/attackby(obj/item/attacking_item, mob/user) - if(istype(attacking_item, /obj/item/stack/rods)) - var/obj/item/stack/rods/O = attacking_item - if(O.get_amount() >= 12) - - to_chat(user, SPAN_NOTICE("You are trying to add metal bars to \the [src].")) - - if (!do_after(user, 2 SECONDS, src)) - return - - to_chat(user, SPAN_NOTICE("You add metal bars to \the [src].")) - O.use(12) - new /obj/item/trap/animal/large(src.loc) - qdel(src) - return - else - to_chat(user, SPAN_WARNING("You need at least 12 rods to complete \the [src].")) - else if(istype(attacking_item, /obj/item/screwdriver)) - return - else - ..() - /** * # Tripwire trap * @@ -687,7 +170,7 @@ /obj/item/trap/tripwire name = "tripwire trap" desc = "A piece of cable coil strung between two metal rods. Low-tech, but reliable." - icon_base = "tripwire" + icon_state = "tripwire" color = COLOR_RED layer = UNDERDOOR // so you can't cover it with items @@ -699,7 +182,7 @@ /obj/item/trap/tripwire/update_icon() underlays = null - icon_state = "[icon_base][deployed]" + icon_state = "[initial(icon_state)][deployed]" var/image/I = image(icon, null, "[icon_state]_base") I.appearance_flags = RESET_COLOR underlays = list(I) @@ -717,7 +200,8 @@ user.visible_message(SPAN_NOTICE("\The [user] starts to take \the [src] down..."), SPAN_NOTICE("You begin taking \the [src] down..."), SPAN_WARNING("You hear a latch click followed by the slow creaking of a spring.")) if(do_after(user, 6 SECONDS)) deployed = FALSE - anchored = FALSE + if(unsecure_on_disarm) + anchored = FALSE update_icon() /obj/item/trap/tripwire/attack_mob(mob/living/L) @@ -741,8 +225,7 @@ name = "punji trap" desc = "An horrendous trap." icon = 'icons/obj/item/traps/punji.dmi' - icon_base = "punji" - icon_state = "punji0" + icon_state = "punji" var/message = null /obj/item/trap/punji/Crossed(atom/movable/AM) @@ -848,5 +331,547 @@ /obj/item/trap/punji/deployed deployed = TRUE anchored = TRUE - icon_state = "punji1" +/** + * # Animal trap + * + * Used to catch small animals like rats, lizards, and chicks + */ +/obj/item/trap/animal + name = "small trap" + desc = "A small mechanical trap that's used to catch small animals like rats, lizards, and chicks." + icon_state = "small" + throwforce = 2 + force = 1 + w_class = ITEMSIZE_SMALL + origin_tech = list(TECH_ENGINEERING = 1) + matter = list(DEFAULT_WALL_MATERIAL = 1750) + health = 100 + can_astar_pass = CANASTARPASS_ALWAYS_PROC + time_to_escape = 3 MINUTES + unsecure_on_disarm = FALSE + + /// Whether someone is actively breaking out of this trap + var/breakout = FALSE + + /// The time in deciseconds between shakes when a user is trying to escape + var/shake_time = 5 SECONDS + + /// The world.time of when the last breakout shake callback happened successfully + var/last_shake = 0 + + /// When a mob of this size crosses over the active trap, it'll be caught + var/max_mob_size = MOB_MINISCULE + + /// When deconstructed, it will return these materials + var/list/resources = list(/obj/item/stack/rods = 6) + + /// A weakref to the mob currently held inside the trap + var/datum/weakref/captured = null + +/obj/item/trap/animal/get_trap_examine_text(mob/user, distance, is_adjacent, infix, suffix) + . = list() + if(captured) + var/mob/captured_mob = captured.resolve() + if(captured_mob) + . += SPAN_NOTICE("You can free the creature inside by right clicking the trap and selecting \"Release Animal\".") + if(deployed) + . += SPAN_NOTICE("You can disarm the trap by clicking on it, or by right clicking the trap and selecting \"Deploy / Undeploy Trap\".") + else if(anchored) + . += get_securing_text(secure = FALSE) + if(!captured) + . += SPAN_NOTICE("You can deploy the trap by clicking on it, or by right clicking the trap and selecting \"Deploy / Undeploy Trap\".") + else + . += get_securing_text(secure = TRUE) + . += SPAN_NOTICE("The trap can be deconstructed with a welding tool.") + +/obj/item/trap/animal/proc/get_securing_text(var/secure = TRUE) + if(secure) + return SPAN_NOTICE("You can secure the trap by using a screwdriver on it. This will anchor it to the floor, and ready it for deployment.") + return SPAN_NOTICE("You can unsecure the trap by using a screwdriver on it. This will unanchor it from the floor, allowing it to be moved.") + +/obj/item/trap/animal/MouseDrop_T(atom/dropping, mob/user) + var/mob/living/capturing_mob = dropping + if(!istype(capturing_mob)) + return + + if(captured) + to_chat(user, SPAN_WARNING("\The [src] is already full!")) + return + + if(capturing_mob.mob_size > max_mob_size) + to_chat(user, SPAN_WARNING("[capturing_mob] won't fit in there!")) + return + + if(!do_mob(user, capturing_mob, 3 SECONDS, needhand = FALSE)) + return + + capture(capturing_mob) + +/obj/item/trap/animal/Crossed(atom/movable/AM) + if(!deployed || !anchored) + return + + if(captured) // just in case but this shouldn't happen + return + + capture(AM) + +/obj/item/trap/animal/proc/capture(var/atom/movable/movable_atom, var/msg = 1) + if(!isliving(movable_atom)) + return + + var/mob/living/capturing_mob = movable_atom + if(capturing_mob.mob_size > max_mob_size) + return + + if(msg) + capturing_mob.visible_message(SPAN_DANGER("[capturing_mob] enters \the [src], and it snaps shut with a clatter!"), SPAN_DANGER("You enter \the [src], and it snaps shut with a clatter!"), SPAN_DANGER("You hear a loud metallic snap!")) + + if(capturing_mob.loc != loc) + capturing_mob.forceMove(loc) + + captured = WEAKREF(capturing_mob) + INVOKE_ASYNC(src, PROC_REF(buckle), capturing_mob) + layer = capturing_mob.layer + 0.1 + + playsound(src, 'sound/weapons/beartrap_shut.ogg', 100, 1) + + deployed = FALSE + + shake_animation() + update_icon() + +/obj/item/trap/animal/proc/can_breakout() + if(deployed || !captured) + return FALSE // Trap-door is open, no one is captured. + if(breakout) + return FALSE //Already breaking out. + return TRUE + +/obj/item/trap/animal/proc/breakout_callback(var/mob/living/escapee) + if (QDELETED(escapee)) + return FALSE + + if ((world.time - last_shake) > 5 SECONDS) + playsound(loc, 'sound/effects/grillehit.ogg', 100, TRUE) + shake_animation() + last_shake = world.time + + return TRUE + +// If we are stuck, and need to get out +/obj/item/trap/animal/user_unbuckle(var/mob/living/escapee) + if (!can_breakout()) + return + + escapee.next_move = world.time + 1 SECOND + escapee.last_special = world.time + 1 SECOND + + to_chat(escapee, SPAN_WARNING("You begin to shake and bump the lock of \the [src]. (this will take about [time_to_escape / 600] minutes).")) + visible_message(SPAN_DANGER("\The [src] begins to shake violently! Something is attempting to escape it!")) + + if (!do_after(escapee, time_to_escape, src, extra_checks = CALLBACK(src, PROC_REF(breakout_callback), escapee))) + breakout = FALSE + return + + breakout = FALSE + to_chat(escapee, SPAN_WARNING("You successfully break out!")) + visible_message(SPAN_DANGER("\The [escapee] successfully breaks out of \the [src]!")) + playsound(loc, 'sound/effects/grillehit.ogg', 100, 1) + + release() + +/obj/item/trap/animal/CollidedWith(atom/bumped_atom) + if(deployed) + Crossed(bumped_atom) + return + return ..() + +/obj/item/trap/animal/verb/deploy_trap() + set src in orange(1) + set category = "Object" + set name = "Deploy / Undeploy Trap" + + if(use_check_and_message(usr)) + return + + if(!can_use(usr)) + to_chat(usr, SPAN_WARNING("You cannot use \the [src].")) + return + + if(deployed) + if(captured) + to_chat(usr, SPAN_WARNING("\The [src] already has something inside!")) + return + + disarm_trap(usr) + else + if(!anchored) + to_chat(usr, SPAN_WARNING("\The [src] needs to be secured first!")) + return + + if(captured) + to_chat(usr, SPAN_WARNING("\The [src] already has something inside!")) + return + + deploy(usr) + +/obj/item/trap/animal/verb/release_animal() + set src in orange(1) + set category = "Object" + set name = "Release Animal" + + if(!usr.canmove || usr.stat || usr.restrained()) + return + + if(!ishuman(usr)) + to_chat(usr, SPAN_WARNING("This mob type can't use this verb.")) + return + + var/mob/captured_mob = captured ? captured.resolve() : null + + if(captured_mob) + if(tgui_alert(usr, "Do you want to open the cage and free \the [captured_mob]?", src.name, list("Yes", "No")) == "No") + return + + if(!can_use(usr)) + to_chat(usr, SPAN_WARNING("You cannot use \the [src].")) + return + + if(usr == captured_mob) + to_chat(usr, SPAN_WARNING("You can't open \the [src] from the inside! You'll need to force it open.")) + return + + var/adj = src.Adjacent(usr) + if(!adj) + attack_self(src) + return + + release(usr) + +/obj/item/trap/animal/crush_act() + if(captured) + var/datum/L = captured ? captured.resolve() : null + if(L && isliving(L)) + var/mob/living/LL = L + LL.gib() + new /obj/item/stack/material/steel(get_turf(src)) + qdel(src) + +/obj/item/trap/animal/ex_act(severity) + switch(severity) + if(1) + health -= rand(120, 240) + if(2) + health -= rand(60, 120) + if(3) + health -= rand(30, 60) + + if (health <= 0) + if(captured) + release() + new /obj/item/stack/material/steel(get_turf(src)) + qdel(src) + +/obj/item/trap/animal/bullet_act(var/obj/item/projectile/Proj) + var/mob/living/captured_mob = captured ? captured.resolve() : null + if(captured_mob) + captured_mob.bullet_act(Proj) + +/obj/item/trap/animal/proc/release(var/mob/user, var/turf/target) + if(!target) + target = src.loc + if(user) + visible_message(SPAN_NOTICE("[user] opens \the [src].")) + + var/mob/captured_mob = captured ? captured.resolve() : null + if (!captured_mob) + captured = null + return + + var/msg + if (isliving(captured_mob)) + var/mob/living/living_mob = captured_mob + msg = SPAN_WARNING("[living_mob] runs out of \the [src].") + + unbuckle() + captured = null + visible_message(msg) + shake_animation() + update_icon() + layer = initial(layer) + +/obj/item/trap/animal/attackby(obj/item/attacking_item, mob/user) + if(istype(attacking_item, /obj/item/grab)) + var/obj/item/grab/grab = attacking_item + var/mob/living/capturing_mob = grab.affecting + + if (grab.state == GRAB_PASSIVE || grab.state == GRAB_UPGRADING) + to_chat(user, SPAN_NOTICE("You need a better grip on \the [capturing_mob]!")) + return + + user.visible_message("[SPAN_BOLD(user)] starts putting \the [capturing_mob] into \the [src].", SPAN_NOTICE("You start putting \the [capturing_mob] into \the [src].")) + + if(capturing_mob.mob_size > max_mob_size) + to_chat(user, SPAN_WARNING("\The [capturing_mob] won't fit in there!")) + return + + if (do_mob(user, capturing_mob, 3 SECONDS, needhand = FALSE)) + if(captured?.resolve()) + return + capture(capturing_mob) + + else if(attacking_item.iswelder()) + var/obj/item/weldingtool/WT = attacking_item + if(!WT.isOn()) + to_chat(user, SPAN_WARNING("\The [WT] is off!")) + return + + user.visible_message(SPAN_NOTICE("[user] is trying to slice \the [src] open!"), SPAN_NOTICE("You are trying to slice \the [src] open!")) + + if(WT.use_tool(src, user, 6 SECONDS, volume = 50)) + if(WT.use(2, user)) + user.visible_message(SPAN_NOTICE("[user] slices \the [src] open!"), SPAN_NOTICE("You slice \the [src] open!")) + for(var/resource_path in resources) + new resource_path(loc, resources[resource_path]) + release(user) + qdel(src) + + else if(attacking_item.isscrewdriver()) + var/turf/T = get_turf(src) + if(!T) + to_chat(user, SPAN_WARNING("There is nothing to secure \the [src] to!")) + return + + user.visible_message("[SPAN_BOLD(user)] starts [anchored ? "un" : "" ]securing \the [src]...", SPAN_NOTICE("You start [anchored ? "un" : "" ]securing \the [src]...")) + + if(attacking_item.use_tool(src, user, 3 SECONDS, volume = 50)) + density = !density + anchored = !anchored + if(!anchored) + deployed = FALSE + user.visible_message("[SPAN_BOLD(user)] [anchored ? "" : "un" ]secures \the [src]!", SPAN_NOTICE("You [anchored ? "" : "un" ]secure \the [src]!")) + update_icon() + + else + ..() + +/obj/item/trap/animal/Move() + ..() + if(captured) + var/datum/M = captured.resolve() + if(isliving(M)) + var/mob/living/L = M + if(L && buckled.buckled_to == src) + L.forceMove(loc) + else if(L) + captured = null + else + captured = null + +/obj/item/trap/animal/attack_hand(mob/user) + if(user.loc == src || captured) + return + + if(deployed) + disarm_trap(user) + else if(anchored) + deploy(user) + else + ..() + +/obj/item/trap/animal/proc/pass_without_trace(var/mob/user, var/chance_to_trigger = 0) + user.visible_message("[SPAN_BOLD(user)] tries moving around \the [src] without triggering it.", SPAN_NOTICE("You try to carefully move around \the [src] without triggering it.")) + if(do_after(user, 2 SECONDS, src)) + if(chance_to_trigger && prob(chance_to_trigger)) + user.forceMove(loc) + user.visible_message(SPAN_WARNING("[user] accidentally triggers \the [src]!"), SPAN_WARNING("You accidentally trigger \the [src]!")) + capture(user) + else + user.forceMove(loc) + user.visible_message("[SPAN_BOLD(user)] successfully moves around \the [src] without triggering it.", SPAN_NOTICE("You successfully move around \the [src] without triggering it.")) + +/obj/item/trap/animal/MouseDrop(over_object, src_location, over_location) + if(!isliving(usr) || !src.Adjacent(usr)) + return + + if(captured) + pass_without_trace(usr) // It's full + return + + else if(iscarbon(usr)) + pass_without_trace(usr) + return + + return ..() + +/obj/item/trap/animal/attack_self(mob/user) + if(!can_use(user)) + to_chat(user, SPAN_WARNING("You cannot use \the [src].")) + return + + if(captured) + release(user, user.loc) + +/obj/item/trap/animal/attack(var/target, mob/living/user) + if(!deployed) + return + + if(captured) // It is full + return + + if(isliving(target)) + var/mob/living/capturing_mob = target + if(capturing_mob.mob_size > max_mob_size) + to_chat(user, SPAN_WARNING("\The [capturing_mob] doesn't fit in \the [src]!")) + return + user.visible_message(SPAN_WARNING("[user] traps \the [capturing_mob] inside of \the [src]."), SPAN_WARNING("You trap [capturing_mob] inside of the \the [src]!"), SPAN_DANGER("You hear a loud metallic snap!")) + capture(capturing_mob, FALSE) + else + ..() + +/obj/item/trap/animal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + return TRUE + +/obj/item/trap/animal/CanAStarPass(to_dir, datum/can_pass_info/pass_info) + return TRUE + + +/** + * # Animal trap (Medium) + * + * Used to catch medium animals like cats, monkeys, nymphs, and wayward maintenance drones + */ +/obj/item/trap/animal/medium + name = "medium trap" + desc = "A medium mechanical trap that is used to catch moderately-sized animals like cats, monkeys, nymphs, and wayward maintenance drones." + icon_state = "medium" + throwforce = 4 + force = 11 + w_class = ITEMSIZE_LARGE + origin_tech = list(TECH_ENGINEERING = 3) + matter = list(DEFAULT_WALL_MATERIAL = 5750) + + max_mob_size = MOB_SMALL + resources = list(/obj/item/stack/rods = 12) + +/** + * # Animal trap (Large) + * + * Used to catch larger animals, from spiders and dogs to bears and even larger mammals + */ +/obj/item/trap/animal/large + name = "large trap" + desc = "A large mechanical trap that is used to catch larger animals, from spiders and dogs to bears and even larger mammals." + icon_state = "large" + throwforce = 6 + force = 10 + w_class = 6 + density = TRUE + origin_tech = list(TECH_ENGINEERING = 3) + matter = list(DEFAULT_WALL_MATERIAL = 15750) + + max_mob_size = 20 + resources = list(/obj/item/stack/rods = 12, /obj/item/stack/material/steel = 4) + +/obj/item/trap/animal/large/get_securing_text(var/secure = TRUE) + if(secure) + return SPAN_NOTICE("You can secure the trap by using a wrench on it. This will anchor it to the floor, and ready it for deployment.") + return SPAN_NOTICE("You can unsecure the trap by using a wrench on it. This will unanchor it from the floor, allowing it to be moved.") + +/obj/item/trap/animal/large/attack_hand(mob/user) + if(user == buckled) + return + else if(!anchored) + to_chat(user, SPAN_WARNING("You need to anchor \the [src] first!")) + else if(captured) + to_chat(user, SPAN_WARNING("You can't deploy \the [src] with something caught!")) + else + ..() + +/obj/item/trap/animal/large/attackby(obj/item/attacking_item, mob/user) + if(attacking_item.iswrench()) + var/turf/T = get_turf(src) + if(!T) + to_chat(user, SPAN_WARNING("There is nothing to secure \the [src] to!")) + return + + if(anchored && deployed) + to_chat(user, SPAN_WARNING("You can't do that while \the [src] is deployed! Undeploy it first.")) + return + + user.visible_message(SPAN_NOTICE("[user] begins [anchored ? "un" : "" ]securing \the [src]!"), + SPAN_NOTICE("You begin [anchored ? "un" : "" ]securing \the [src]!")) + + if(attacking_item.use_tool(src, user, 30, volume = 50)) + anchored = !anchored + user.visible_message(SPAN_NOTICE("[user] [anchored ? "" : "un" ]secures \the [src]!"), + SPAN_NOTICE("You [anchored ? "" : "un" ]secure \the [src]!")) + + else if(attacking_item.isscrewdriver()) + // Unlike smaller traps, screwdriver shouldn't work on this. + return + else + ..() + +/obj/item/trap/animal/large/MouseDrop(over_object, src_location, over_location) + if(captured) + to_chat(usr, SPAN_WARNING("The trap door's down, you can't get through there!")) + return + + if(!src.Adjacent(usr)) + return + + if(!ishuman(usr)) + ..() + return + + var/trigger_chance = 0 + if(usr.a_intent == I_HELP) + trigger_chance = 100 + else if(usr.a_intent != I_HURT) + trigger_chance = 50 + + pass_without_trace(usr, trigger_chance) + +/obj/item/trap/animal/large/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + if(deployed) + return TRUE + return FALSE + +/obj/item/large_trap_foundation + name = "large trap foundation" + desc = "A metal foundation for large trap, it is missing metals rods to hold the prey." + icon = 'icons/obj/item/traps/traps.dmi' + icon_state = "large_foundation" + throwforce = 4 + force = 11 + w_class = ITEMSIZE_HUGE + +/obj/item/large_trap_foundation/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) + . = ..() + . += SPAN_NOTICE("\The [src] can be turned into a large trap by attaching twelve metal rods to it.") + +/obj/item/large_trap_foundation/attackby(obj/item/attacking_item, mob/user) + if(istype(attacking_item, /obj/item/stack/rods)) + var/obj/item/stack/rods/O = attacking_item + if(O.get_amount() >= 12) + user.visible_message("[SPAN_BOLD(user)] starts adding metal bars to \the [src]...", SPAN_NOTICE("You start adding metal bars to \the [src]...")) + + if (!do_after(user, 2 SECONDS, src)) + return + O.use(12) + + var/obj/item/trap/animal/large/new_trap = new /obj/item/trap/animal/large(src.loc) + user.visible_message("[SPAN_BOLD(user)] finishes constructing \the [new_trap].", SPAN_NOTICE("You finish constructing \the [new_trap].")) + + qdel(src) + return + else + to_chat(user, SPAN_WARNING("You need at least twelve rods to complete \the [src].")) + return + else if(istype(attacking_item, /obj/item/screwdriver)) + return + else + ..() diff --git a/code/game/sound.dm b/code/game/sound.dm index aa13ca867e9..5afcee00b5d 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -530,6 +530,12 @@ 'sound/effects/bulletflyby3.ogg' ) +/singleton/sound_category/screwdriver_sound + sounds = list( + 'sound/items/Screwdriver.ogg', + 'sound/items/Screwdriver2.ogg' + ) + /singleton/sound_category/crowbar_sound sounds = list( 'sound/items/crowbar1.ogg', diff --git a/code/modules/mob/living/simple_animal/hostile/sarlacc.dm b/code/modules/mob/living/simple_animal/hostile/sarlacc.dm index 7c3c7d87bf6..43c41aef92f 100644 --- a/code/modules/mob/living/simple_animal/hostile/sarlacc.dm +++ b/code/modules/mob/living/simple_animal/hostile/sarlacc.dm @@ -21,6 +21,9 @@ var/mob/living/simple_animal/hostile/greatworm/originator var/mob/living/captive +/obj/item/trap/sarlacc/get_trap_examine_text(mob/user, distance, is_adjacent, infix, suffix) + return list() + /obj/item/trap/sarlacc/Destroy() if(originator) originator = null diff --git a/html/changelogs/geeves-trap_tweaks.yml b/html/changelogs/geeves-trap_tweaks.yml new file mode 100644 index 00000000000..77784708cb5 --- /dev/null +++ b/html/changelogs/geeves-trap_tweaks.yml @@ -0,0 +1,10 @@ +author: Geeves + +delete-after: True + +changes: + - rscadd: "Animal traps now work based off mob size, instead of having a specific mapped in list for animals it can or cannot capture." + - rscadd: "Added new examine text to traps that help explain how to use them." + - rscadd: "Added a verb to deploy / undeploy traps." + - rscadd: "Tweaked a couple interactions with traps, mechanically and grammatically." + - bugfix: "Fixed a lot of bugs and issues with traps."