diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 1d47ae409b3..056bc25be5d 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -129,6 +129,9 @@
#define TINT_BLIND 3 //Threshold of tint level to obscure vision fully
#define EYE_SHINE_THRESHOLD 6 //dark_view threshold past which a humanoid's eyes will 'shine' in the dark.
+#define EMOTE_VISUAL 1 //A mob emote is visual
+#define EMOTE_SOUND 2 //A mob emote is sound
+
//Human sub-species
#define isshadowling(A) (is_species(A, /datum/species/shadow/ling))
#define isshadowlinglesser(A) (is_species(A, /datum/species/shadow/ling/lesser))
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index df7206a7914..8228e490a38 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -10,46 +10,67 @@
bomb_name = "voice-activated bomb"
- describe()
- if(recorded || listening)
- return "A meter on [src] flickers with every nearby sound."
- else
- return "[src] is deactivated."
+/obj/item/assembly/voice/describe()
+ if(recorded || listening)
+ return "A meter on [src] flickers with every nearby sound."
+ else
+ return "[src] is deactivated."
- hear_talk(mob/living/M as mob, msg)
- hear_input(M, msg, 0)
+/obj/item/assembly/voice/hear_talk(mob/living/M as mob, msg)
+ hear_input(M, msg, 0)
- hear_message(mob/living/M as mob, msg)
+/obj/item/assembly/voice/hear_message(mob/living/M as mob, msg)
hear_input(M, msg, 1)
- proc/hear_input(mob/living/M as mob, msg, type)
- if(!istype(M,/mob/living))
- return
- if(listening)
- recorded = msg
- recorded_type = type
- listening = 0
- var/turf/T = get_turf(src) //otherwise it won't work in hand
- T.visible_message("[bicon(src)] beeps, \"Activation message is [type ? "the sound when one [recorded]" : "'[recorded]'."]\"")
- else
- if(findtext(msg, recorded) && type == recorded_type)
- pulse(0)
- var/turf/T = get_turf(src) //otherwise it won't work in hand
- T.visible_message("[bicon(src)] beeps!")
+/obj/item/assembly/voice/proc/hear_input(mob/living/M as mob, msg, type)
+ if(!istype(M,/mob/living))
+ return
+ if(listening)
+ recorded = msg
+ recorded_type = type
+ listening = 0
+ var/turf/T = get_turf(src) //otherwise it won't work in hand
+ T.visible_message("[bicon(src)] beeps, \"Activation message is [type ? "the sound when one [recorded]" : "'[recorded]'."]\"")
+ else if(findtext(msg, recorded) && type == recorded_type)
+ pulse(0)
+ var/turf/T = get_turf(src) //otherwise it won't work in hand
+ T.visible_message("[bicon(src)] beeps!")
- activate()
- return // previously this toggled listning when not in a holder, that's a little silly. It was only called in attack_self that way.
+/obj/item/assembly/voice/activate()
+ return // previously this toggled listning when not in a holder, that's a little silly. It was only called in attack_self that way.
- attack_self(mob/user)
- if(!user || !secured) return 0
+/obj/item/assembly/voice/attack_self(mob/user)
+ if(!user || !secured) return 0
- listening = !listening
- var/turf/T = get_turf(src)
- T.visible_message("[bicon(src)] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
- return 1
+ listening = !listening
+ var/turf/T = get_turf(src)
+ T.visible_message("[bicon(src)] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
+ return 1
- toggle_secure()
- . = ..()
- listening = 0
\ No newline at end of file
+/obj/item/assembly/voice/toggle_secure()
+ . = ..()
+ listening = 0
+
+/obj/item/assembly/voice/noise
+ name = "noise sensor"
+ desc = "A simple noise sensor that triggers on vocalizations other then speech."
+ icon_state = "voice"
+ materials = list(MAT_METAL=100, MAT_GLASS=10)
+ origin_tech = "magnets=1;engineering=1"
+ bomb_name = "noise-activated bomb"
+
+/obj/item/assembly/voice/noise/attack_self(mob/user)
+ return
+
+/obj/item/assembly/voice/noise/describe()
+ return "[src] does not appear to have any controls."
+
+/obj/item/assembly/voice/noise/hear_talk(mob/living/M as mob, msg)
+ return
+
+/obj/item/assembly/voice/hear_message(mob/living/M as mob, msg)
+ pulse(0)
+ var/turf/T = get_turf(src) //otherwise it won't work in hand
+ T.visible_message("[bicon(src)] beeps!")
\ No newline at end of file
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index 22f293aedfc..fba251f71f8 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -120,6 +120,78 @@
mute = MUZZLE_MUTE_NONE
security_lock = TRUE
locked = FALSE
+ materials = list(MAT_METAL=500, MAT_GLASS=50)
+
+/obj/item/clothing/mask/muzzle/safety/shock
+ name = "shock muzzle"
+ desc = "A muzzle designed to prevent biting. This one is fitted with a behavior correction system."
+ var/obj/item/assembly/trigger = null
+ origin_tech = "materials=1;engineering=1"
+ materials = list(MAT_METAL=500, MAT_GLASS=50)
+
+/obj/item/clothing/mask/muzzle/safety/shock/attackby(obj/item/W, mob/user, params)
+ if(isscrewdriver(W) && trigger)
+ to_chat(user, "You disassemble [src].")
+ trigger.forceMove(get_turf(user))
+ trigger.master = null
+ trigger.holder = null
+ trigger = null
+ return TRUE
+ else if(istype(W, /obj/item/assembly/signaler) || istype(W, /obj/item/assembly/voice))
+ if(istype(trigger, /obj/item/assembly/signaler) || istype(trigger, /obj/item/assembly/voice))
+ to_chat(user, "Something is already attached to [src].")
+ return FALSE
+ if(!user.drop_item())
+ to_chat(user, "You are unable to insert [W] into [src].")
+ return FALSE
+ trigger = W
+ trigger.forceMove(src)
+ trigger.master = src
+ trigger.holder = src
+ to_chat(user, "You attach the [W] to [src].")
+ return TRUE
+ else if(istype(W, /obj/item/assembly))
+ to_chat(user, "That won't fit in [src]. Perhaps a signaler or voice analyzer would?")
+ return FALSE
+
+ return ..()
+
+
+/obj/item/clothing/mask/muzzle/safety/shock/proc/can_shock(obj/item/clothing/C)
+ if(istype(C))
+ if(isliving(C.loc))
+ return C.loc
+ else if(isliving(loc))
+ return loc
+ return FALSE
+
+/obj/item/clothing/mask/muzzle/safety/shock/proc/process_activation(var/obj/D, var/normal = 1, var/special = 1)
+ visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*")
+ var/mob/M = can_shock(loc)
+ if(M)
+ to_chat(M, "You feel a sharp shock!")
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(3, 1, M)
+ s.start()
+
+ M.Weaken(5)
+ M.Stuttering(1)
+ M.Jitter(20)
+ return
+
+/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM as mob|obj)
+ if(trigger)
+ trigger.HasProximity(AM)
+
+
+/obj/item/clothing/mask/muzzle/safety/shock/hear_talk(mob/living/M as mob, msg)
+ if(trigger)
+ trigger.hear_talk(M, msg)
+
+/obj/item/clothing/mask/muzzle/safety/shock/hear_message(mob/living/M as mob, msg)
+ if(trigger)
+ trigger.hear_message(M, msg)
+
/obj/item/clothing/mask/surgical
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index 623f5b6b353..fffda3a2158 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -24,15 +24,20 @@
return target
// All mobs should have custom emote, really..
-/mob/proc/custom_emote(var/m_type=1,var/message = null)
+/mob/proc/custom_emote(var/m_type=EMOTE_VISUAL,var/message = null)
if(stat || !use_me && usr == src)
if(usr)
to_chat(usr, "You are unable to emote.")
return
- var/muzzled = istype(src.wear_mask, /obj/item/clothing/mask/muzzle)
- if(m_type == 2 && muzzled) return
+ var/muzzled = is_muzzled()
+ if(muzzled)
+ var/obj/item/clothing/mask/muzzle/M = wear_mask
+ if(m_type == EMOTE_SOUND && M.mute >= MUZZLE_MUTE_MUFFLE)
+ return //Not all muzzles block sound
+ if(!can_speak())
+ return
var/input
if(!message)
@@ -63,7 +68,7 @@
// Type 1 (Visual) emotes are sent to anyone in view of the item
- if(m_type & 1)
+ if(m_type & EMOTE_VISUAL)
var/list/can_see = get_mobs_in_view(1,src) //Allows silicon & mmi mobs carried around to see the emotes of the person carrying them around.
can_see |= viewers(src,null)
for(var/mob/O in can_see)
@@ -80,7 +85,7 @@
// Type 2 (Audible) emotes are sent to anyone in hear range
// of the *LOCATION* -- this is important for pAIs to be heard
- else if(m_type & 2)
+ else if(m_type & EMOTE_SOUND)
for(var/mob/O in get_mobs_in_view(7,src))
if(O.status_flags & PASSEMOTES)
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index c1eb8641820..69653dfe1e8 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -10,6 +10,10 @@
act = copytext(act, 1, t1)
var/muzzled = is_muzzled()
+ if(muzzled)
+ var/obj/item/clothing/mask/muzzle/M = wear_mask
+ if(M.mute == MUZZLE_MUTE_NONE)
+ muzzled = 0 //Not all muzzles block sound
if(!can_speak())
muzzled = 1
//var/m_type = 1
diff --git a/code/modules/mob/living/update_status.dm b/code/modules/mob/living/update_status.dm
index f7498392c02..c7f9bdd0d96 100644
--- a/code/modules/mob/living/update_status.dm
+++ b/code/modules/mob/living/update_status.dm
@@ -49,7 +49,14 @@
// Whether the mob is capable of talking
/mob/living/can_speak()
- return !(silent || (disabilities & MUTE) || is_muzzled())
+ if(!(silent || (disabilities & MUTE)))
+ if(is_muzzled())
+ var/obj/item/clothing/mask/muzzle/M = wear_mask
+ if(M.mute >= MUZZLE_MUTE_MUFFLE)
+ return FALSE
+ return TRUE
+ else
+ return FALSE
// Whether the mob is capable of standing or not
/mob/living/proc/can_stand()
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index bba85a5ff00..79dd9fb53f9 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -551,6 +551,14 @@
build_path = /obj/item/assembly/voice
category = list("initial", "Miscellaneous")
+/datum/design/noise_analyser
+ name = "Noise Analyser"
+ id = "Noise_analyser"
+ build_type = AUTOLATHE
+ materials = list(MAT_METAL = 100, MAT_GLASS = 10)
+ build_path = /obj/item/assembly/voice/noise
+ category = list("initial", "Miscellaneous")
+
/datum/design/light_tube
name = "Light Tube"
id = "light_tube"
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index 8bdf6771df1..1036bb6e188 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -60,3 +60,23 @@
materials = list(MAT_METAL = 500, MAT_GLASS = 300)
build_path = /obj/item/camera/digital
category = list("Miscellaneous")
+
+/datum/design/safety_muzzle
+ name = "Safety Muzzle"
+ desc = "Produce a lockable muzzle keyed to security ID cards"
+ id = "safetymuzzle"
+ req_tech = list("materials" = 1)
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL=500, MAT_GLASS=50)
+ build_path = /obj/item/clothing/mask/muzzle/safety
+ category = list("Miscellaneous")
+
+/datum/design/shock_muzzle
+ name = "Shock Muzzle"
+ desc = "Produce a modified safety muzzle that includes an electric shock pack and a slot for a trigger assembly."
+ id = "shockmuzzle"
+ req_tech = list("materials" = 1, "engineering" = 1)
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL=500, MAT_GLASS=50)
+ build_path = /obj/item/clothing/mask/muzzle/safety/shock
+ category = list("Miscellaneous")