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."