diff --git a/code/__DEFINES/emotes.dm b/code/__DEFINES/emotes.dm
index 99704553f99..5880f171d7f 100644
--- a/code/__DEFINES/emotes.dm
+++ b/code/__DEFINES/emotes.dm
@@ -37,9 +37,12 @@
// Each mob can only play an emote with audio every AUDIO_EMOTE_COOLDOWN seconds, unless a given emote overrides its own audio cooldown.
-/// Default cooldown for audio that comes from emotes.
+/// Default cooldown for emote-emitted audio.
#define AUDIO_EMOTE_COOLDOWN (5 SECONDS)
+/// Cooldown for emotes that are emitted unintentionally, to prevent them from getting audibly spammy.
+#define AUDIO_EMOTE_UNINTENTIONAL_COOLDOWN (2 SECONDS)
+
// Emote parameter types
// If nothing is passed in for a target, this can determine the possible target.
diff --git a/code/datums/emote.dm b/code/datums/emote.dm
index 1b2edf9e488..d4a7663ee76 100644
--- a/code/datums/emote.dm
+++ b/code/datums/emote.dm
@@ -97,6 +97,10 @@
var/cooldown = DEFAULT_EMOTE_COOLDOWN
/// How long is the cooldown on the audio of the emote, if it has one?
var/audio_cooldown = AUDIO_EMOTE_COOLDOWN
+ /// If the emote is triggered unintentionally, how long would that cooldown be?
+ var/unintentional_audio_cooldown = AUDIO_EMOTE_UNINTENTIONAL_COOLDOWN
+ /// If true, an emote will completely bypass any cooldown when called unintentionally. Necessary for things like deathgasp.
+ var/bypass_unintentional_cooldown = FALSE
/// How loud is the audio emote?
var/volume = 50
@@ -184,7 +188,7 @@
var/sound_volume = get_volume(user)
// If our sound emote is forced by code, don't worry about cooldowns at all.
if(tmp_sound && should_play_sound(user, intentional) && sound_volume > 0)
- if(!intentional || user.start_audio_emote_cooldown(audio_cooldown))
+ if(bypass_unintentional_cooldown || user.start_audio_emote_cooldown(intentional, intentional ? audio_cooldown : unintentional_audio_cooldown))
play_sound_effect(user, intentional, tmp_sound, sound_volume)
if(msg)
@@ -303,7 +307,7 @@
return TRUE
// if our emote would play sound but another audio emote is on cooldown, prevent this emote from being used.
// Note that this only applies to intentional emotes
- if(get_sound(user) && should_play_sound(user, intentional) && !user.can_use_audio_emote())
+ if(get_sound(user) && should_play_sound(user, intentional) && !user.can_use_audio_emote(intentional))
return FALSE
var/cooldown_in_use
if(!isnull(user.emote_cooldown_override))
diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm
index ee9d2730220..344aafb872d 100644
--- a/code/modules/mob/living/carbon/human/human_emote.dm
+++ b/code/modules/mob/living/carbon/human/human_emote.dm
@@ -99,6 +99,7 @@
vary = TRUE
age_based = TRUE
cooldown = 5 SECONDS
+ unintentional_audio_cooldown = 3.5 SECONDS
mob_type_blacklist_typecache = list(
/mob/living/carbon/human/monkey, // screech instead
/mob/living/silicon // Robot sounds
diff --git a/code/modules/mob/living/living_emote.dm b/code/modules/mob/living/living_emote.dm
index 7270e45f6e4..62df24c9b5c 100644
--- a/code/modules/mob/living/living_emote.dm
+++ b/code/modules/mob/living/living_emote.dm
@@ -71,6 +71,7 @@
cooldown = 10 SECONDS
volume = 40
unintentional_stat_allowed = DEAD
+ bypass_unintentional_cooldown = TRUE // again, this absolutely MUST play when a user dies, if it can.
message = "seizes up and falls limp, their eyes dead and lifeless..."
message_alien = "seizes up and falls limp, their eyes dead and lifeless..."
message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening."
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index adc24e80b39..171a50e1c30 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1542,7 +1542,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
if(!is_component_functioning("power cell") || !cell || !cell.charge)
- if(!start_audio_emote_cooldown(10 SECONDS))
+ if(!start_audio_emote_cooldown(TRUE, 10 SECONDS))
to_chat(src, "The low-power capacitor for your speaker system is still recharging, please try again later.")
return
visible_message("The power warning light on [src] flashes urgently.",\
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 1882292a24c..278e424fcbb 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -102,12 +102,14 @@
/// Cooldown on audio effects from emotes.
var/audio_emote_cd_status = EMOTE_READY
+ /// Cooldown on audio effects from unintentional emotes.
+ var/audio_emote_unintentional_cd_status = EMOTE_READY
+
/// Override for cooldowns on non-audio emotes. Should be a number in deciseconds.
var/emote_cooldown_override = null
/// Tracks last uses of emotes for cooldown purposes
var/list/emotes_used
- var/list/emotes_on_cooldown
var/job = null //Living
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 5ac82f4e641..584b619b0b5 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -755,8 +755,9 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM
/**
* Helper proc to determine if a mob can use emotes that make sound or not.
*/
-/mob/proc/can_use_audio_emote()
- switch(audio_emote_cd_status)
+/mob/proc/can_use_audio_emote(intentional)
+ var/emote_status = intentional ? audio_emote_cd_status : audio_emote_unintentional_cd_status
+ switch(emote_status)
if(EMOTE_INFINITE) // Spam those emotes
return TRUE
if(EMOTE_ADMIN_BLOCKED) // Cooldown emotes were disabled by an admin, prevent use
@@ -769,24 +770,37 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM
CRASH("Invalid emote type")
/**
- * # Start the cooldown for an emote that plays audio.
+ * Start the cooldown for an emote that plays audio.
*
- * * cooldown: The amount of time that should be waited before any other audio emote can fire.
+ * Arguments:
+ * * intentional - Whether or not the user deliberately triggered this emote.
+ * * cooldown - The amount of time that should be waited before any other audio emote can fire.
*/
-/mob/proc/start_audio_emote_cooldown(cooldown = AUDIO_EMOTE_COOLDOWN)
- if(!can_use_audio_emote())
+/mob/proc/start_audio_emote_cooldown(intentional, cooldown = AUDIO_EMOTE_COOLDOWN)
+ if(!can_use_audio_emote(intentional))
return FALSE
- if(audio_emote_cd_status == EMOTE_READY)
- audio_emote_cd_status = EMOTE_ON_COOLDOWN // Starting cooldown
- addtimer(CALLBACK(src, .proc/on_audio_emote_cooldown_end), cooldown)
+ var/cooldown_source = intentional ? audio_emote_cd_status : audio_emote_unintentional_cd_status
+
+ if(cooldown_source == EMOTE_READY)
+ // we do have to juggle between cooldowns a little bit, but this lets us keep them on separate cooldowns so
+ // a user screaming every five seconds doesn't prevent them from sneezing.
+ if(intentional)
+ audio_emote_cd_status = EMOTE_ON_COOLDOWN // Starting cooldown
+ else
+ audio_emote_unintentional_cd_status = EMOTE_ON_COOLDOWN
+ addtimer(CALLBACK(src, .proc/on_audio_emote_cooldown_end, intentional), cooldown)
return TRUE // proceed with emote
-/mob/proc/on_audio_emote_cooldown_end()
- if(audio_emote_cd_status == EMOTE_ON_COOLDOWN)
- // only reset emotes that probably weren't set by an admin
- audio_emote_cd_status = EMOTE_READY
+/mob/proc/on_audio_emote_cooldown_end(intentional)
+ if(intentional)
+ if(audio_emote_cd_status == EMOTE_ON_COOLDOWN)
+ // only reset to ready if we're in a cooldown state
+ audio_emote_cd_status = EMOTE_READY
+ else
+ if(audio_emote_unintentional_cd_status == EMOTE_ON_COOLDOWN)
+ audio_emote_unintentional_cd_status = EMOTE_READY
/proc/stat_to_text(stat)
switch(stat)