diff --git a/beestation.dme b/beestation.dme
index b821820446fe3..c42e283f1e289 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -340,6 +340,7 @@
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\icons.dm"
+#include "code\_globalvars\lists\keybindings.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
#include "code\_globalvars\lists\mobs.dm"
@@ -917,6 +918,7 @@
#include "code\datums\keybinding\carbon.dm"
#include "code\datums\keybinding\client.dm"
#include "code\datums\keybinding\communication.dm"
+#include "code\datums\keybinding\emote.dm"
#include "code\datums\keybinding\human.dm"
#include "code\datums\keybinding\keybinding.dm"
#include "code\datums\keybinding\living.dm"
diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm
index 4a0502d319458..b9568ebdbf5db 100644
--- a/code/__DEFINES/cooldowns.dm
+++ b/code/__DEFINES/cooldowns.dm
@@ -48,6 +48,8 @@
#define COOLDOWN_MECHA_MELEE_ATTACK "mecha_melee"
#define COOLDOWN_MECHA_SMOKE "mecha_smoke"
+#define COOLDOWN_EMOTE_WINDOW "emote_window"
+
//TIMER COOLDOWN MACROS
#define COMSIG_CD_STOP(cd_index) "cooldown_[cd_index]"
diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm
index 7fad19e2aa6d3..17a654b8fc4c4 100644
--- a/code/__DEFINES/keybinding.dm
+++ b/code/__DEFINES/keybinding.dm
@@ -2,6 +2,7 @@
//General
#define COMSIG_KB_ACTIVATED (1<<0)
+#define COMSIG_KB_EMOTE "keybinding_emote_down"
//Admin
#define COMSIG_KB_ADMIN_ASAY_DOWN "keybinding_admin_asay_down"
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 9198ea42d2bb7..b65a99f086ba3 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -73,13 +73,7 @@
GLOB.hair_gradients_list[H.name] = H
// Keybindings
- for(var/KB in subtypesof(/datum/keybinding))
- var/datum/keybinding/keybinding = KB
- if(!initial(keybinding.keys) || !initial(keybinding.keybind_signal))
- continue
- var/datum/keybinding/instance = new keybinding
- GLOB.keybindings_by_name[instance.name] = instance
- LAZYADD(GLOB.keybindings_by_name_to_key[instance.name], LAZYCOPY(instance.keys))
+ init_keybindings()
init_crafting_recipes(GLOB.crafting_recipes)
diff --git a/code/_globalvars/lists/keybindings.dm b/code/_globalvars/lists/keybindings.dm
new file mode 100644
index 0000000000000..2bf6abc648409
--- /dev/null
+++ b/code/_globalvars/lists/keybindings.dm
@@ -0,0 +1,22 @@
+/// Creates and sorts all the keybinding datums
+/proc/init_keybindings()
+ for(var/KB in subtypesof(/datum/keybinding))
+ var/datum/keybinding/keybinding = KB
+ if(!initial(keybinding.keybind_signal) || !initial(keybinding.name))
+ continue
+ add_keybinding(new keybinding)
+ init_emote_keybinds()
+
+/// Adds an instanced keybinding to the global tracker
+/proc/add_keybinding(datum/keybinding/instance)
+ GLOB.keybindings_by_name[instance.name] = instance
+ LAZYADD(GLOB.keybindings_by_name_to_key[instance.name], LAZYCOPY(instance.keys))
+
+/proc/init_emote_keybinds()
+ for(var/i in subtypesof(/datum/emote))
+ var/datum/emote/faketype = i
+ if(!initial(faketype.key))
+ continue
+ var/datum/keybinding/emote/emote_kb = new
+ emote_kb.link_to_emote(faketype)
+ add_keybinding(emote_kb)
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index d88793bc03b9a..c193fd55f9845 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -170,7 +170,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/split_personality)
return FALSE
/mob/living/split_personality/emote(act, m_type = null, message = null, intentional = FALSE)
- return
+ return FALSE
///////////////BRAINWASHING////////////////////
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index 7eeed5c3d7f8d..ce17ae29792a6 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -26,9 +26,21 @@
var/sound
/// Volume to play the sound at
var/sound_volume = 50
- /// Whether to vary the pitch of the sound played
+ /// Do we vary the pitch of the sound played
var/vary = FALSE
var/only_forced_audio = FALSE //can only code call this event instead of the player.
+ /// The cooldown between the uses of the emote.
+ var/cooldown = 0.5 SECONDS
+ /// How long is the shared emote cooldown triggered by this emote?
+ var/general_emote_audio_cooldown = 7 SECONDS
+ /// How long is the specific emote cooldown triggered by this emote?
+ var/specific_emote_audio_cooldown = 10 SECONDS
+ /// Every time a emote is made, it increases this counter by one. When the integer equals cooldown_integer_ceiling, the mob is forced into a cooldown period
+ var/cooldown_integer = 0
+ /// Maximum amount of emotes that can be made
+ var/cooldown_integer_ceiling = 3
+ /// Does this emote's sound ignore walls?
+ var/sound_wall_ignore = FALSE
// Animated emote stuff
// ~~~~~~~~~~~~~~~~~~~
@@ -61,18 +73,27 @@
if(!name)
name = key
+/**
+ * Handles the modifications and execution of emotes.
+ *
+ * Arguments:
+ * * user - Person that is trying to send the emote.
+ * * params - Parameters added after the emote.
+ * * type_override - Override to the current emote_type.
+ * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
+ *
+ */
/datum/emote/proc/run_emote(mob/user, params, type_override, intentional = FALSE)
SHOULD_CALL_PARENT(TRUE)
- if(!can_run_emote(user, TRUE, intentional))
- return FALSE
if((emote_type & EMOTE_ANIMATED) && emote_length > 0)
var/image/I = image(overlay_icon, user, overlay_icon_state, ABOVE_MOB_LAYER, 0, overlay_x_offset, overlay_y_offset)
flick_overlay_view(I, user, emote_length)
var/tmp_sound = get_sound(user)
- if(tmp_sound && (!only_forced_audio || !intentional))
- playsound(user, tmp_sound, sound_volume, vary)
+ if(tmp_sound && should_play_sound(user, intentional) && !TIMER_COOLDOWN_CHECK(user, "audible_emote_cooldown") && !TIMER_COOLDOWN_CHECK(user, type))
+ run_cooldown_integer(user)
+ playsound(source = user, soundin = tmp_sound, vol = sound_volume, vary = vary, ignore_walls = sound_wall_ignore)
var/msg = select_message_type(user, intentional)
if(params && message_param)
@@ -86,7 +107,7 @@
I.trigger(key, L)
if(!msg)
- return TRUE
+ return
user.log_message(msg, LOG_EMOTE)
@@ -124,7 +145,7 @@
viewer.show_message("[user] [msg]", MSG_AUDIBLE)
else if(is_visual)
viewer.show_message("[user] [msg]", MSG_VISUAL)
- return TRUE // Early exit so no dchat message
+ return // Early exit so no dchat message
// The emote has some important information, and should always be shown to the user
else if(is_important)
@@ -168,6 +189,51 @@
if(!ghost?.client.prefs?.read_player_preference(/datum/preference/toggle/chat_ghostsight))
continue
to_chat(ghost, "[FOLLOW_LINK(ghost, user)] [dchatmsg]")
+ return
+
+/datum/emote/proc/run_cooldown_integer(mob/user)
+ //If we do one audible emote, then do nothing, we eventually should purge our list
+ cooldown_integer_window(user)
+ cooldown_integer += 1
+ //debug
+ //user.balloon_alert(user, "[cooldown_integer]")
+ check_cooldown_integer(user)
+
+/datum/emote/proc/check_cooldown_integer(mob/user)
+ if(cooldown_integer >= cooldown_integer_ceiling)
+ to_chat(user, "[name] emote limit reached")
+ TIMER_COOLDOWN_START(user, type, specific_emote_audio_cooldown)
+ TIMER_COOLDOWN_START(user, "general_emote_audio_cooldown", general_emote_audio_cooldown)
+ //We used up all our usable emotes, now we set the integer back to zero and wait the long wait
+ cooldown_integer = initial(cooldown_integer)
+
+/datum/emote/proc/cooldown_integer_window(mob/user)
+ if(TIMER_COOLDOWN_CHECK(user, COOLDOWN_EMOTE_WINDOW))
+ TIMER_COOLDOWN_START(user, COOLDOWN_EMOTE_WINDOW, 4 SECONDS)
+ else
+ //We did a few emotes, but didnt use them all up, reset our integer
+ cooldown_integer = initial(cooldown_integer)
+
+/**
+ * For handling emote cooldown, return true to allow the emote to happen.
+ *
+ * Arguments:
+ * * user - Person that is trying to send the emote.
+ * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
+ *
+ * Returns FALSE if the cooldown is not over, TRUE if the cooldown is over.
+ */
+/datum/emote/proc/check_cooldown(mob/user, intentional)
+ if(!intentional)
+ return TRUE
+
+ if(user.emotes_used && user.emotes_used[src] + cooldown > world.time)
+ return FALSE
+
+ if(!user.emotes_used)
+ user.emotes_used = list()
+
+ user.emotes_used[src] = world.time
return TRUE
/datum/emote/proc/get_sound(mob/living/user)
@@ -208,7 +274,7 @@
/datum/emote/proc/select_param(mob/user, params)
return replacetext(message_param, "%t", params)
-/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE)
+/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE, params)
. = TRUE
if(!is_type_in_typecache(user, mob_type_allowed_typecache))
return FALSE
@@ -237,6 +303,39 @@
if(HAS_TRAIT(L, TRAIT_EMOTEMUTE))
return FALSE
+/**
+ * Check to see if the user should play a sound when performing the emote.
+ *
+ * Arguments:
+ * * user - Person that is doing the emote.
+ * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
+ *
+ * Returns a bool about whether or not the user should play a sound when performing the emote.
+ */
+/datum/emote/proc/should_play_sound(mob/user, intentional = FALSE)
+ if(emote_type & EMOTE_AUDIBLE && !hands_use_check)
+ if(HAS_TRAIT(user, TRAIT_MUTE))
+ return FALSE
+ if(ishuman(user))
+ var/mob/living/carbon/human/loud_mouth = user
+ if(loud_mouth.mind?.miming) // vow of silence prevents outloud noises
+ return FALSE
+ if(!loud_mouth.getorganslot(ORGAN_SLOT_TONGUE))
+ return FALSE
+
+ if(only_forced_audio && intentional)
+ return FALSE
+ return TRUE
+
+/**
+* Allows the intrepid coder to send a basic emote
+* Takes text as input, sends it out to those who need to know after some light parsing
+* If you need something more complex, make it into a datum emote
+* Arguments:
+* * text - The text to send out
+*
+* Returns TRUE if it was able to run the emote, FALSE otherwise.
+*/
/mob/proc/manual_emote(text) //Just override the song and dance
. = TRUE
if(stat != CONSCIOUS)
diff --git a/code/datums/keybinding/_defines.dm b/code/datums/keybinding/_defines.dm
index fcca453b332b5..f9bd57f107578 100644
--- a/code/datums/keybinding/_defines.dm
+++ b/code/datums/keybinding/_defines.dm
@@ -1,4 +1,5 @@
#define CATEGORY_CLIENT "CLIENT"
+#define CATEGORY_EMOTE "EMOTE"
#define CATEGORY_ADMIN "ADMIN"
#define CATEGORY_CARBON "CARBON"
#define CATEGORY_HUMAN "HUMAN"
@@ -8,11 +9,12 @@
#define CATEGORY_COMMUNICATION "COMMUNICATION"
#define WEIGHT_HIGHEST 0
-#define WEIGHT_CLIENT 10
-#define WEIGHT_ADMIN 20
-#define WEIGHT_MOB 30
-#define WEIGHT_LIVING 40
-#define WEIGHT_DEAD 50
-#define WEIGHT_ROBOT 60
+#define WEIGHT_ADMIN 10
+#define WEIGHT_CLIENT 20
+#define WEIGHT_ROBOT 30
+#define WEIGHT_MOB 40
+#define WEIGHT_LIVING 50
+#define WEIGHT_DEAD 60
+#define WEIGHT_EMOTE 70
#define WEIGHT_AI 80
#define WEIGHT_LOWEST 999
diff --git a/code/datums/keybinding/emote.dm b/code/datums/keybinding/emote.dm
new file mode 100644
index 0000000000000..6c58aba8733a8
--- /dev/null
+++ b/code/datums/keybinding/emote.dm
@@ -0,0 +1,17 @@
+/datum/keybinding/emote
+ category = CATEGORY_EMOTE
+ weight = WEIGHT_EMOTE
+ keybind_signal = COMSIG_KB_EMOTE
+ var/emote_key
+
+/datum/keybinding/emote/proc/link_to_emote(datum/emote/faketype)
+ keys = list("Unbound")
+ emote_key = initial(faketype.key)
+ name = initial(faketype.key)
+ full_name = capitalize(initial(faketype.key))
+
+/datum/keybinding/emote/down(client/user)
+ . = ..()
+ if(.)
+ return
+ return user.mob.emote(emote_key, intentional=TRUE)
diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm
index 34ff413b6aae9..02213718f884c 100644
--- a/code/modules/mob/camera/camera.dm
+++ b/code/modules/mob/camera/camera.dm
@@ -28,7 +28,7 @@
return FALSE
/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE)
- return
+ return FALSE
// Cameras can't fall
/mob/camera/has_gravity(turf/T)
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index edff4142e2d86..2a3ffed3b9c0e 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -1,24 +1,32 @@
//The code execution of the emote datum is located at code/datums/emotes.dm
/mob/proc/emote(act, m_type, message, intentional = FALSE)
- act = LOWER_TEXT(act)
var/param = message
var/custom_param = findchar(act, " ")
if(custom_param)
param = copytext(act, custom_param + length(act[custom_param]))
act = copytext(act, 1, custom_param)
+ act = LOWER_TEXT(act)
var/list/key_emotes = GLOB.emote_list[act]
if(!length(key_emotes))
if(intentional)
to_chat(src, "'[act]' emote does not exist. Say *help for a list.")
- return
- for(var/datum/emote/P in key_emotes)
- if(P.run_emote(src, param, m_type, intentional))
- SEND_SIGNAL(src, COMSIG_MOB_EMOTE, P, act, m_type, message, intentional)
- return
- if(intentional)
+ return FALSE
+ var/silenced = FALSE
+ for(var/datum/emote/emote in key_emotes)
+ if(!emote.check_cooldown(src, intentional))
+ silenced = TRUE
+ continue
+ if(!emote.can_run_emote(src, TRUE, intentional, param))
+ continue
+ emote.run_emote(src, param, m_type, intentional)
+ SEND_SIGNAL(src, COMSIG_MOB_EMOTE, emote, act, m_type, message, intentional)
+ //SEND_SIGNAL(src, COMSIG_MOB_EMOTED(emote.key))
+ return TRUE
+ if(intentional && !silenced)
to_chat(src, "Unusable emote '[act]'. Say *help for a list.")
+ return FALSE
/datum/emote/flip
key = "flip"
@@ -29,12 +37,33 @@
emote_type = EMOTE_VISIBLE
/datum/emote/flip/run_emote(mob/user, params , type_override, intentional)
+ . = ..()
+ user.SpinAnimation(7,1)
+ if(isliving(user))
+ var/mob/living/L = user
+ L.confused += 2
+
+/datum/emote/flip/check_cooldown(mob/user, intentional)
. = ..()
if(.)
- user.SpinAnimation(7,1)
- if(isliving(user) && intentional)
- var/mob/living/L = user
- L.confused += 2
+ return
+ if(!can_run_emote(user, intentional=intentional))
+ return
+ if(isliving(user))
+ var/mob/living/flippy_mcgee = user
+ if(prob(20))
+ flippy_mcgee.Knockdown(1 SECONDS)
+ flippy_mcgee.visible_message(
+ "[flippy_mcgee] attempts to do a flip and falls over, what a doofus!",
+ "You attempt to do a flip while still off balance from the last flip and fall down!"
+ )
+ if(prob(50))
+ flippy_mcgee.adjustBruteLoss(1)
+ else
+ flippy_mcgee.visible_message(
+ "[flippy_mcgee] stumbles a bit after their flip.",
+ "You stumble a bit from still being off balance from your last flip."
+ )
/datum/emote/spin
key = "spin"
@@ -46,19 +75,18 @@
/datum/emote/spin/run_emote(mob/user, params , type_override, intentional)
. = ..()
- if(.)
- user.spin(20, 1)
- if(isliving(user) && intentional)
- var/mob/living/L = user
- L.confused += 2
- if(iscyborg(user) && user.has_buckled_mobs())
- var/mob/living/silicon/robot/R = user
- var/datum/component/riding/riding_datum = R.GetComponent(/datum/component/riding)
- if(riding_datum)
- for(var/mob/M in R.buckled_mobs)
- riding_datum.force_dismount(M)
- else
- R.unbuckle_all_mobs()
+ user.spin(20, 1)
+ if(isliving(user))
+ var/mob/living/L = user
+ L.confused += 2
+ if(iscyborg(user) && user.has_buckled_mobs())
+ var/mob/living/silicon/robot/R = user
+ var/datum/component/riding/riding_datum = R.GetComponent(/datum/component/riding)
+ if(riding_datum)
+ for(var/mob/M in R.buckled_mobs)
+ riding_datum.force_dismount(M)
+ else
+ R.unbuckle_all_mobs()
/datum/emote/inhale
key = "inhale"
diff --git a/code/modules/mob/living/brain/emote.dm b/code/modules/mob/living/brain/emote.dm
index 75599e892a25d..c93e3acf4b24a 100644
--- a/code/modules/mob/living/brain/emote.dm
+++ b/code/modules/mob/living/brain/emote.dm
@@ -3,7 +3,7 @@
mob_type_blacklist_typecache = list()
emote_type = EMOTE_AUDIBLE
-/datum/emote/brain/can_run_emote(mob/user, status_check = TRUE, intentional)
+/datum/emote/brain/can_run_emote(mob/user, status_check = TRUE, intentional, params)
. = ..()
var/mob/living/brain/B = user
if(!istype(B) || (!(B.container && istype(B.container, /obj/item/mmi))))
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 953f5ee00dbee..f7890fed9be1a 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -120,13 +120,15 @@
key_third_person = "screams"
message = "screams"
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
+ specific_emote_audio_cooldown = 5 SECONDS
+ cooldown_integer_ceiling = 2
vary = TRUE
-/datum/emote/living/carbon/human/scream/get_sound(mob/living/user)
- if(!ishuman(user) || user.mind?.miming)
+/datum/emote/living/carbon/human/scream/get_sound(mob/living/carbon/human/user)
+ if(!istype(user))
return
- var/mob/living/carbon/H = user
- return H.dna?.species?.get_scream_sound(H)
+
+ return user.dna.species.get_scream_sound(user)
/datum/emote/living/carbon/human/pale
key = "pale"
@@ -168,8 +170,6 @@
/datum/emote/living/carbon/human/wag/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(!.)
- return
var/mob/living/carbon/human/H = user
var/obj/item/organ/tail/tail = H?.getorganslot(ORGAN_SLOT_TAIL)
if(!tail)
@@ -177,8 +177,6 @@
tail.toggle_wag(H)
/datum/emote/living/carbon/human/wag/can_run_emote(mob/user, status_check = TRUE , intentional)
- if(!..())
- return FALSE
var/mob/living/carbon/human/H = user
return istype(H?.getorganslot(ORGAN_SLOT_TAIL), /obj/item/organ/tail)
@@ -197,9 +195,8 @@
/datum/emote/living/carbon/human/wing/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(.)
- var/mob/living/carbon/human/H = user
- H.Togglewings()
+ var/mob/living/carbon/human/H = user
+ H.Togglewings()
/datum/emote/living/carbon/human/wing/select_message_type(mob/user, intentional)
. = ..()
@@ -209,9 +206,7 @@
else
. = "closes " + message
-/datum/emote/living/carbon/human/wing/can_run_emote(mob/user, status_check = TRUE, intentional)
- if(!..())
- return FALSE
+/datum/emote/living/carbon/human/wing/can_run_emote(mob/user, status_check = TRUE, intentional, params)
var/mob/living/carbon/human/H = user
if(H.dna && H.dna.species)
if(H.dna.features["wings"] != "None")
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 2bc9d918a7cef..22b956bbc14e6 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -76,9 +76,9 @@
/datum/emote/living/collapse/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(. && isliving(user) && intentional)
- var/mob/living/L = user
- L.Unconscious(40)
+ if(isliving(user) && intentional)
+ var/mob/living/living = user
+ living.Unconscious(4 SECONDS)
/datum/emote/living/dance
key = "dance"
@@ -99,16 +99,16 @@
message_ipc = "gives one shrill beep before falling limp, their monitor flashing blue before completely shutting off"
message_simple = "stops moving"
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE | EMOTE_IMPORTANT
+ cooldown = (7.5 SECONDS)
stat_allowed = HARD_CRIT
-/datum/emote/living/deathgasp/run_emote(mob/user, params, type_override, intentional)
+/datum/emote/living/deathgasp/run_emote(mob/living/user, params, type_override, intentional)
var/mob/living/simple_animal/S = user
if(istype(S) && S.deathmessage)
message_simple = S.deathmessage
. = ..()
message_simple = initial(message_simple)
- var/mob/living/living_user = user
- if(!. && !living_user.can_speak_vocal() || living_user.getOxyLoss() >= 50)
+ if(!user.can_speak_vocal() || user.getOxyLoss() >= 50)
return //stop the sound if oxyloss too high/cant speak
var/mob/living/carbon/carbon_user = user
// For masks that give unique death sounds
@@ -132,9 +132,9 @@
/datum/emote/living/faint/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(. && isliving(user) && intentional)
- var/mob/living/L = user
- L.SetSleeping(200)
+ if(isliving(user) && intentional)
+ var/mob/living/living = user
+ living.SetSleeping(20 SECONDS)
/datum/emote/living/flap
key = "flap"
@@ -146,7 +146,7 @@
/datum/emote/living/flap/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(. && ishuman(user))
+ if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/wings/wings = H.getorganslot(ORGAN_SLOT_WINGS)
if(H.Togglewings())
@@ -224,10 +224,11 @@
/datum/emote/living/jump/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_y = user.pixel_y + 4, time = 0.1 SECONDS)
animate(pixel_y = user.pixel_y - 4, time = 0.1 SECONDS)
+ if(iscarbon(user))
+ var/mob/living/carbon/jumps_till_drops = user
+ jumps_till_drops.adjustStaminaLoss(10, forced = TRUE)
/datum/emote/living/jump/get_sound(mob/living/user)
return 'sound/weapons/thudswoosh.ogg'
@@ -245,12 +246,12 @@
message = "laughs"
message_mime = "laughs silently"
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
+ specific_emote_audio_cooldown = 5 SECONDS
+ cooldown_integer_ceiling = 3
vary = TRUE
/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional)
. = ..()
- if(!.)
- return FALSE
if(iscarbon(user))
var/mob/living/carbon/C = user
return !C.silent
@@ -309,6 +310,7 @@
message_mime = "acts out a scream"
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized scream.
+ sound_wall_ignore = TRUE
/datum/emote/living/scream/select_message_type(mob/user, intentional)
. = ..()
@@ -336,8 +338,6 @@
#define SHIVER_LOOP_DURATION (1 SECONDS)
/datum/emote/living/shiver/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_x = user.pixel_x + 1, time = 0.1 SECONDS)
for(var/i in 1 to SHIVER_LOOP_DURATION / (0.2 SECONDS)) //desired total duration divided by the iteration duration to give the necessary iteration count
animate(pixel_x = user.pixel_x - 1, time = 0.1 SECONDS)
@@ -404,9 +404,9 @@
/datum/emote/living/surrender/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(. && isliving(user) && intentional)
- var/mob/living/L = user
- L.Paralyze(200)
+ if(isliving(user) && intentional)
+ var/mob/living/living = user
+ living.Paralyze(20 SECONDS)
/datum/emote/living/sway
key = "sway"
@@ -416,8 +416,6 @@
/datum/emote/living/sway/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_x = user.pixel_x + 2, time = 0.5 SECONDS)
for(var/i in 1 to 2)
animate(pixel_x = user.pixel_x - 4, time = 1.0 SECONDS)
@@ -433,8 +431,6 @@
#define TREMBLE_LOOP_DURATION (4.4 SECONDS)
/datum/emote/living/tremble/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_x = user.pixel_x + 2, time = 0.2 SECONDS)
for(var/i in 1 to TREMBLE_LOOP_DURATION / (0.4 SECONDS)) //desired total duration divided by the iteration duration to give the necessary iteration count
animate(pixel_x = user.pixel_x - 2, time = 0.2 SECONDS)
@@ -450,8 +446,6 @@
/datum/emote/living/twitch/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_x = user.pixel_x - 1, time = 0.1 SECONDS)
animate(pixel_x = user.pixel_x + 1, time = 0.1 SECONDS)
animate(time = 0.1 SECONDS)
@@ -466,8 +460,6 @@
/datum/emote/living/twitch_s/run_emote(mob/living/user, params, type_override, intentional)
. = ..()
- if(!.)
- return FALSE
animate(user, pixel_x = user.pixel_x - 1, time = 0.1 SECONDS)
animate(pixel_x = user.pixel_x + 1, time = 0.1 SECONDS)
@@ -504,28 +496,23 @@
mob_type_blacklist_typecache = /mob/living/brain
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
-/datum/emote/living/custom/can_run_emote(mob/user, status_check, intentional)
- . = ..() && intentional
-
-/datum/emote/living/custom/proc/check_invalid(mob/user, input)
- var/static/regex/stop_bad_mime = regex(@"says|exclaims|yells|asks")
- if(stop_bad_mime.Find(input, 1, 1))
- to_chat(user, "Invalid emote.")
- return TRUE
- return FALSE
-
-/datum/emote/living/custom/run_emote(mob/user, params, type_override = null, intentional = FALSE)
- if(!can_run_emote(user, TRUE, intentional))
+/datum/emote/living/custom/can_run_emote(mob/user, status_check, intentional, params)
+ . = ..()
+ if(!. || !intentional)
return FALSE
- if(is_banned_from(user.ckey, "Emote"))
+
+ if(!isnull(user.ckey) && is_banned_from(user.ckey, "Emote"))
to_chat(user, "You cannot send custom emotes (banned).")
return FALSE
- else if(QDELETED(user))
+
+ if(QDELETED(user))
return FALSE
- else if(user.client && user.client.prefs.muted & MUTE_IC)
+
+ if(user.client && user.client.prefs.muted & MUTE_IC)
to_chat(user, "You cannot send IC messages (muted).")
return FALSE
- else if(!params)
+
+ if(!params)
var/custom_emote = stripped_input(user, "Choose an emote to display.")
if(custom_emote && !check_invalid(user, custom_emote))
var/list/emote_list = list("Audible", "Visible", "Both")
@@ -540,8 +527,17 @@
message = user.say_emphasis(custom_emote)
else
message = params
- if(type_override)
- emote_type = type_override
+
+/datum/emote/living/custom/proc/check_invalid(mob/user, input)
+ var/static/regex/stop_bad_mime = regex(@"says|exclaims|yells|asks")
+ if(stop_bad_mime.Find(input, 1, 1))
+ to_chat(user, "Invalid emote.")
+ return TRUE
+ return FALSE
+
+/datum/emote/living/custom/run_emote(mob/user, params, type_override = null, intentional = FALSE)
+ if(params && type_override)
+ emote_type = type_override
. = ..()
message = null
emote_type = null
diff --git a/code/modules/mob/living/silicon/ai/emote.dm b/code/modules/mob/living/silicon/ai/emote.dm
index 7114c04d502c3..ab600d808ee57 100644
--- a/code/modules/mob/living/silicon/ai/emote.dm
+++ b/code/modules/mob/living/silicon/ai/emote.dm
@@ -9,8 +9,6 @@
/datum/emote/ai/emotion_display/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(!.)
- return
var/mob/living/silicon/ai/ai = user
var/turf/ai_turf = get_turf(ai)
@@ -80,9 +78,6 @@
/datum/emote/ai/emotion_display/friend_computer/run_emote(mob/user, params, type_override, intentional)
. = ..()
- if(!.)
- return
-
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
if(!frequency)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 67f9136df2696..8f2fc41b34612 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -374,8 +374,8 @@
/mob/living/simple_animal/emote(act, m_type=1, message = null, intentional = FALSE)
if(stat)
- return
- . = ..()
+ return FALSE
+ return ..()
/mob/living/simple_animal/proc/set_varspeed(var_value)
speed = var_value
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 9969981ccadf7..b72f36346b73e 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -216,6 +216,9 @@ CREATION_TEST_IGNORE_SELF(/mob)
var/memory_throttle_time = 0
+ /// Used for tracking last uses of emotes for cooldown purposes
+ var/list/emotes_used
+
///Whether the mob is updating glide size when movespeed updates or not
var/updating_glide_size = TRUE
diff --git a/code/modules/multiz/movement/mob/living_zmove.dm b/code/modules/multiz/movement/mob/living_zmove.dm
index e2e70f180612d..ed700a757f5f9 100644
--- a/code/modules/multiz/movement/mob/living_zmove.dm
+++ b/code/modules/multiz/movement/mob/living_zmove.dm
@@ -64,7 +64,7 @@
if(MOVETYPE_NONE_JUMP)
visible_message("[src] jumps into the air, as if [p_they()] expected to float... Gravity pulls [p_them()] back down quickly.", "You try jumping into the space above you. Gravity pulls you back down quickly.")
do_jump_animation()
- adjustStaminaLoss(15, forced = TRUE)
+ adjustStaminaLoss(10, forced = TRUE)
return FALSE
if(MOVETYPE_JAUNT)
move_verb = "moving"
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 7788bda1ba68c..d92e2d64a9e60 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -27,6 +27,7 @@
*/
#include "dcs_get_id_from_elements.dm"
#include "dynamic_ruleset_sanity.dm"
+#include "emoting.dm"
#include "enumerables.dm"
#ifdef REFERENCE_TRACKING_DEBUG //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter
diff --git a/code/modules/unit_tests/emoting.dm b/code/modules/unit_tests/emoting.dm
new file mode 100644
index 0000000000000..a9a0bb51e9c40
--- /dev/null
+++ b/code/modules/unit_tests/emoting.dm
@@ -0,0 +1,23 @@
+/datum/unit_test/emoting
+ var/emotes_used = 0
+
+/datum/unit_test/emoting/Run()
+ var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)
+ RegisterSignal(human, COMSIG_MOB_EMOTE, PROC_REF(on_emote_used))
+
+ human.say("*shrug")
+ TEST_ASSERT_EQUAL(emotes_used, 1, "Human did not shrug")
+
+ human.say("*beep")
+ TEST_ASSERT_EQUAL(emotes_used, 1, "Human beeped, when that should be restricted to silicons")
+
+ human.setOxyLoss(140)
+
+ TEST_ASSERT(human.stat != CONSCIOUS, "Human is somehow conscious after receiving suffocation damage")
+
+ human.say("*shrug")
+ TEST_ASSERT_EQUAL(emotes_used, 1, "Human shrugged while unconscious")
+
+/datum/unit_test/emoting/proc/on_emote_used()
+ SIGNAL_HANDLER
+ emotes_used += 1