diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index b6f3cca841f1..ffaca963b280 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -108,6 +108,30 @@
#define MOVABLE_SAY_QUOTE_MESSAGE_SPANS 2
#define MOVABLE_SAY_QUOTE_MESSAGE_MODS 3
+/// Sent from /atom/movable/proc/say_mod() to pick a say verb
+#define COMSIG_MOVABLE_SAY_MOD "movable_say_mod"
+ // Saymod handler categories
+ /// Say mod for strings that end in a question mark
+ #define SAY_MOD_ASK "ask"
+ /// Default saymod for no special handling
+ #define SAY_MOD_DEFAULT "say"
+ /// Say mod for strings that end in an exclamation mark
+ #define SAY_MOD_EXCLAIM "exclaim"
+ /// Say mod for strings that are sung
+ #define SAY_MOD_SING "sing"
+ /// Say mod for strings that are whispered
+ #define SAY_MOD_WHISPER "whisper"
+ /// Say mod for strings that are whispered, but also has two exclamations marks
+ #define SAY_MOD_WHISPER_YELL "whisper_yell"
+ /// Say mod for strings that have two exclamation marks
+ #define SAY_MOD_YELL "yell"
+
+ // Priorities for say mods
+ #define SAY_MOD_PRIORITY_DEFAULT -10
+ #define SAY_MOD_PRIORITY_LOW 0
+ #define SAY_MOD_PRIORITY_MEDIUM 10
+ #define SAY_MOD_PRIORITY_HIGH 20
+
/// From /datum/element/immerse/proc/add_submerge_overlay(): (visual_overlay)
#define COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY "movable_edit_unique_submerge_overlay"
/// From base of area/Exited(): (area/left, direction)
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 0262ab084376..8aca08d8cd76 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -32,6 +32,10 @@
#define MODE_WHISPER "whisper"
#define MODE_WHISPER_CRIT "whispercrit"
+#define SHOUT_MODE "the type of shout"
+#define MODE_YELL "yell" // !!
+#define MODE_SHOUT "shout" // !
+
#define MODE_DEPARTMENT "department"
#define MODE_KEY_DEPARTMENT "h"
#define MODE_TOKEN_DEPARTMENT ":h"
@@ -132,3 +136,6 @@
///Defines for priorities for the bubble_icon_override comp
#define BUBBLE_ICON_PRIORITY_ACCESSORY 2
#define BUBBLE_ICON_PRIORITY_ORGAN 1
+
+/// Applies a span to a message if the span is not null, otherwise just uses the text
+#define CONDITIONAL_SPAN(span, text) (span ? "[text]" : text)
diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm
index 6324d8c1e9bc..e4bbc1a6188d 100644
--- a/code/__DEFINES/span.dm
+++ b/code/__DEFINES/span.dm
@@ -47,9 +47,10 @@
#define span_deadsay(str) ("" + str + "")
#define span_deconversion_message(str) ("" + str + "")
#define span_drone(str) ("" + str + "")
+#define span_emote(str) ("" + str + "")
#define span_engradio(str) ("" + str + "")
-#define span_extremelybig(str) ("" + str + "")
#define span_enteradio(str) ("" + str + "")
+#define span_extremelybig(str) ("" + str + "")
#define span_game_say(str) ("" + str + "")
#define span_ghostalert(str) ("" + str + "")
#define span_green(str) ("" + str + "")
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index a908f3a47c66..4b101d5de657 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -135,10 +135,6 @@
if (!ismob(target))
extra_classes |= "small"
- // Why are you yelling?
- if(copytext_char(text, -2) == "!!")
- extra_classes |= SPAN_YELL
-
var/list/prefixes
var/chat_color_name_to_use
diff --git a/code/datums/components/sign_language.dm b/code/datums/components/sign_language.dm
index 858bb4f61553..4ff9282deb76 100644
--- a/code/datums/components/sign_language.dm
+++ b/code/datums/components/sign_language.dm
@@ -31,6 +31,16 @@
var/regex/omissions = new ("\[!?\]", "g")
/// The action for toggling sign language.
var/datum/action/innate/sign_language/linked_action
+ /// Saymods for sign language users, overrides default saymods
+ var/static/list/say_mods = list(
+ SAY_MOD_ASK = "signs",
+ SAY_MOD_DEFAULT = "signs",
+ SAY_MOD_EXCLAIM = "signs",
+ SAY_MOD_SING = "rythmically signs",
+ SAY_MOD_WHISPER = "subtly signs",
+ SAY_MOD_WHISPER_YELL = "signs",
+ SAY_MOD_YELL = "emphatically signs",
+ )
/// Replace specific characters in the input string with periods.
/datum/component/sign_language/proc/sanitize_message(input)
@@ -67,18 +77,10 @@
SIGNAL_HANDLER
var/mob/living/carbon/carbon_parent = parent
- var/obj/item/organ/tongue/tongue = carbon_parent.get_organ_slot(ORGAN_SLOT_TONGUE)
- if(tongue)
- tongue.temp_say_mod = "signs"
//this speech relies on hands, which we have our own way of garbling speech when they're occupied, so we can have this always on
ADD_TRAIT(carbon_parent, TRAIT_SPEAKS_CLEARLY, SPEAKING_FROM_HANDS)
- carbon_parent.verb_ask = "signs"
- carbon_parent.verb_exclaim = "signs"
- carbon_parent.verb_whisper = "subtly signs"
- carbon_parent.verb_sing = "rythmically signs"
- carbon_parent.verb_yell = "emphatically signs"
carbon_parent.bubble_icon = "signlang"
- RegisterSignal(carbon_parent, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_added_organ))
+ RegisterSignal(carbon_parent, COMSIG_MOVABLE_SAY_MOD, PROC_REF(handle_saymod))
RegisterSignal(carbon_parent, COMSIG_MOB_TRY_SPEECH, PROC_REF(on_try_speech))
RegisterSignal(carbon_parent, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(on_treat_living_message))
RegisterSignal(carbon_parent, COMSIG_MOVABLE_USING_RADIO, PROC_REF(on_using_radio))
@@ -94,18 +96,10 @@
SIGNAL_HANDLER
var/mob/living/carbon/carbon_parent = parent
- var/obj/item/organ/tongue/tongue = carbon_parent.get_organ_slot(ORGAN_SLOT_TONGUE)
- if(tongue)
- tongue.temp_say_mod = ""
REMOVE_TRAIT(carbon_parent, TRAIT_SPEAKS_CLEARLY, SPEAKING_FROM_HANDS)
- carbon_parent.verb_ask = initial(carbon_parent.verb_ask)
- carbon_parent.verb_exclaim = initial(carbon_parent.verb_exclaim)
- carbon_parent.verb_whisper = initial(carbon_parent.verb_whisper)
- carbon_parent.verb_sing = initial(carbon_parent.verb_sing)
- carbon_parent.verb_yell = initial(carbon_parent.verb_yell)
carbon_parent.bubble_icon = initial(carbon_parent.bubble_icon)
UnregisterSignal(carbon_parent, list(
- COMSIG_CARBON_GAIN_ORGAN,
+ COMSIG_MOVABLE_SAY_MOD,
COMSIG_MOB_TRY_SPEECH,
COMSIG_LIVING_TREAT_MESSAGE,
COMSIG_MOVABLE_USING_RADIO,
@@ -117,13 +111,11 @@
///Signal proc for [COMSIG_CARBON_GAIN_ORGAN]
///Applies the new say mod to any tongues that have appeared!
-/datum/component/sign_language/proc/on_added_organ(mob/living/source, obj/item/organ/new_organ)
+/datum/component/sign_language/proc/handle_saymod(mob/living/source, datum/saymod_selector/selector)
SIGNAL_HANDLER
- if(!istype(new_organ, /obj/item/organ/tongue))
- return
- var/obj/item/organ/tongue/new_tongue = new_organ
- new_tongue.temp_say_mod = "signs"
+ for(var/saymod_type, saymod_verb in say_mods)
+ selector.add_saymod(saymod_type, saymod_verb, SAY_MOD_PRIORITY_DEFAULT + 1)
/// Signal proc for [COMSIG_MOB_TRY_SPEECH]
/// Sign languagers can always speak regardless of they're mute (as long as they're not mimes)
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index d0a71dfcd243..c5ab3c2ac607 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -62,6 +62,8 @@
var/can_message_change = FALSE
/// How long is the cooldown on the audio of the emote, if it has one?
var/audio_cooldown = 2 SECONDS
+ /// Range of the emotes messages
+ var/emote_range = DEFAULT_MESSAGE_RANGE
/datum/emote/New()
switch(mob_type_allowed_typecache)
@@ -136,7 +138,7 @@
// Emote doesn't get printed to chat, runechat only
if(emote_type & EMOTE_RUNECHAT)
- for(var/mob/viewer as anything in viewers(user))
+ for(var/mob/viewer as anything in viewers(emote_range, user))
if(isnull(viewer.client))
continue
if(!is_important && viewer != user && (!is_visual || !is_audible))
@@ -144,6 +146,10 @@
continue
if(is_visual && viewer.is_blind())
continue
+
+ var/personal_msg = msg
+ var/distance_span = viewer.get_distance_based_span(get_dist(user, viewer) - (is_visual ? 4 : 2)) // -2 to -4 effective distance
+
if(user.runechat_prefs_check(viewer, EMOTE_MESSAGE))
viewer.create_chat_message(
speaker = user,
@@ -151,22 +157,28 @@
runechat_flags = EMOTE_MESSAGE,
)
else if(is_important)
- to_chat(viewer, "[user] [msg]")
+ to_chat(viewer, CONDITIONAL_SPAN(distance_span, span_emote("[user] [personal_msg]")))
else if(is_audible && is_visual)
viewer.show_message(
- "[user] [msg]", MSG_AUDIBLE,
- "You see how [user] [msg]", MSG_VISUAL,
+ CONDITIONAL_SPAN(distance_span, span_emote("[user] [personal_msg]")), MSG_AUDIBLE,
+ CONDITIONAL_SPAN(distance_span, span_emote("You see how [user] [personal_msg]")), MSG_VISUAL,
)
else if(is_audible)
- viewer.show_message("[user] [msg]", MSG_AUDIBLE)
+ viewer.show_message(CONDITIONAL_SPAN(distance_span, span_emote("[user] [personal_msg]")), MSG_AUDIBLE)
else if(is_visual)
- viewer.show_message("[user] [msg]", MSG_VISUAL)
+ viewer.show_message(CONDITIONAL_SPAN(distance_span, span_emote("[user] [personal_msg]")), MSG_VISUAL)
return TRUE // Early exit so no dchat message
// The emote has some important information, and should always be shown to the user
else if(is_important)
- for(var/mob/viewer as anything in viewers(user))
- to_chat(viewer, "[user] [msg]")
+ for(var/mob/viewer as anything in viewers(emote_range, user))
+ if(isnull(viewer.client))
+ continue
+
+ var/personal_msg = msg
+ var/distance_span = viewer.get_distance_based_span(get_dist(user, viewer) - 4) // -4 effective distance
+
+ to_chat(viewer, CONDITIONAL_SPAN(distance_span, span_emote("[user] [personal_msg]")))
if(user.runechat_prefs_check(viewer, EMOTE_MESSAGE))
viewer.create_chat_message(
speaker = user,
@@ -178,9 +190,10 @@
else if(is_visual && is_audible)
user.audible_message(
message = msg,
- deaf_message = "You see how [user] [msg]",
+ deaf_message = span_emote("You see how [user] [msg]"),
self_message = msg,
audible_message_flags = EMOTE_MESSAGE|ALWAYS_SHOW_SELF_MESSAGE,
+ hearing_distance = emote_range,
)
// Emote is entirely audible, no visible component
else if(is_audible)
@@ -188,6 +201,7 @@
message = msg,
self_message = msg,
audible_message_flags = EMOTE_MESSAGE,
+ hearing_distance = emote_range,
)
// Emote is entirely visible, no audible component
else if(is_visual)
@@ -195,6 +209,7 @@
message = msg,
self_message = msg,
visible_message_flags = EMOTE_MESSAGE|ALWAYS_SHOW_SELF_MESSAGE,
+ vision_distance = emote_range
)
else
CRASH("Emote [type] has no valid emote type set!")
@@ -206,7 +221,7 @@
continue
if(!(get_chat_toggles(ghost.client) & CHAT_GHOSTSIGHT))
continue
- to_chat(ghost, "[FOLLOW_LINK(ghost, user)] [dchatmsg]")
+ to_chat(ghost, span_emote("[FOLLOW_LINK(ghost, user)] [dchatmsg]"))
return TRUE
diff --git a/code/datums/status_effects/debuffs/speech_debuffs.dm b/code/datums/status_effects/debuffs/speech_debuffs.dm
index cea3d3cb48a9..17342082ea17 100644
--- a/code/datums/status_effects/debuffs/speech_debuffs.dm
+++ b/code/datums/status_effects/debuffs/speech_debuffs.dm
@@ -16,10 +16,12 @@
/datum/status_effect/speech/on_apply()
RegisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(handle_message))
+ RegisterSignal(owner, COMSIG_MOVABLE_SAY_MOD, PROC_REF(handle_saymod))
return TRUE
/datum/status_effect/speech/on_remove()
UnregisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE)
+ UnregisterSignal(owner, COMSIG_MOVABLE_SAY_MOD)
/**
* Signal proc for [COMSIG_LIVING_TREAT_MESSAGE]
@@ -74,6 +76,10 @@
stack_trace("[type] didn't implement apply_speech.")
return original_char
+/datum/status_effect/speech/proc/handle_saymod(datum/source, datum/saymod_selector/selector)
+ SIGNAL_HANDLER
+ return
+
/datum/status_effect/speech/stutter
id = "stutter"
make_tts_message_original = TRUE
@@ -117,6 +123,9 @@
return some_char
+/datum/status_effect/speech/stutter/handle_saymod(datum/source, datum/saymod_selector/selector)
+ selector.add_saymod(SAY_MOD_DEFAULT, HAS_TRAIT(owner, TRAIT_SIGN_LANG) ? "shakily signs" : "stammers", SAY_MOD_PRIORITY_MEDIUM)
+
/datum/status_effect/speech/stutter/anxiety
id = "anxiety_stutter"
stutter_prob = 5
@@ -133,6 +142,9 @@
stutter_prob = clamp(host_quirk?.calculate_mood_mod() * 0.5, 5, 50)
return ..()
+/datum/status_effect/speech/stutter/anxiety/handle_saymod(datum/source, datum/saymod_selector/selector)
+ return
+
/datum/status_effect/speech/stutter/derpspeech
id = "derp_stutter"
/// The probability of making our message entirely uppercase + adding exclamations
@@ -172,6 +184,9 @@
// Otherwise just return and don't call parent, we already modified our speech
return
+/datum/status_effect/speech/stutter/derpspeech/handle_saymod(datum/source, datum/saymod_selector/selector)
+ selector.add_saymod(SAY_MOD_DEFAULT, HAS_TRAIT(owner, TRAIT_SIGN_LANG) ? "incoherently signs" : "gibbers", SAY_MOD_PRIORITY_MEDIUM + 1)
+
/datum/status_effect/speech/slurring
/// The chance that any given character in a message will be replaced with a common character
var/common_prob = 25
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index 809f23bf4820..e2ab0e2a4f9c 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -314,12 +314,6 @@ GLOBAL_LIST_EMPTY(req_console_ckey_departments)
return data
-/obj/machinery/requests_console/say_mod(input, list/message_mods = list())
- if(spantext_char(input, "!", -3))
- return "blares"
- else
- . = ..()
-
/// Turns the emergency console back to its normal sprite once the emergency has timed out
/obj/machinery/requests_console/proc/clear_emergency()
emergency = null
diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm
index 1be5721c7482..1587977da558 100644
--- a/code/game/objects/items/devices/megaphone.dm
+++ b/code/game/objects/items/devices/megaphone.dm
@@ -39,6 +39,7 @@
else
playsound(loc, 'sound/items/megaphone.ogg', 100, FALSE, TRUE)
spamcheck = world.time + 50
+ speech_args[SPEECH_MODS][MODE_SHOUT] = MODE_YELL
speech_args[SPEECH_SPANS] |= voicespan
/obj/item/megaphone/emag_act(mob/user, obj/item/card/emag/emag_card)
diff --git a/code/game/say.dm b/code/game/say.dm
index d157ed720dba..988f11183869 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -179,7 +179,10 @@ GLOBAL_LIST_INIT(freqtospan, list(
messagepart = " [messagepart]"
- return "[spanpart1][spanpart2][freqpart][message_mods?[SAY_RADIO_ICON]][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]"
+ // modify size of the text based on distance - effective distance decreased by shouting
+ var/distance_span = radio_freq ? null : get_distance_based_span(get_dist(src, speaker) + (message_mods[SHOUT_MODE] ? (message_mods[SHOUT_MODE] == MODE_YELL ? -4 : -1) : 0))
+
+ return CONDITIONAL_SPAN(distance_span, "[spanpart1][spanpart2][freqpart][message_mods?[SAY_RADIO_ICON]][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]")
/atom/movable/proc/compose_track_href(atom/movable/speaker, message_langs, raw_message, radio_freq)
return ""
@@ -194,19 +197,81 @@ GLOBAL_LIST_INIT(freqtospan, list(
* message_mods - A list of message modifiers, i.e. whispering/singing.
*/
/atom/movable/proc/say_mod(input, list/message_mods = list())
- var/ending = copytext_char(input, -1)
- if(copytext_char(input, -2) == "!!")
- return verb_yell
- else if(message_mods[MODE_SING])
- . = verb_sing
- else if(message_mods[WHISPER_MODE])
- . = verb_whisper
- else if(ending == "?")
- return verb_ask
- else if(ending == "!")
- return verb_exclaim
- else
- return get_default_say_verb()
+ var/datum/saymod_selector/selector = new()
+ SEND_SIGNAL(src, COMSIG_MOVABLE_SAY_MOD, selector)
+
+ if(message_mods[SHOUT_MODE])
+ if(message_mods[WHISPER_MODE])
+ var/whisper_yells = selector.get_saymod(SAY_MOD_WHISPER_YELL)
+ if(whisper_yells)
+ return whisper_yells
+
+ var/whispers = selector.get_saymod(SAY_MOD_WHISPER) || verb_whisper
+ var/yells = selector.get_saymod(SAY_MOD_YELL) || verb_yell
+ if(copytext_char(whispers, -1) == "s")
+ whispers = copytext_char(whispers, 1, -1)
+
+ return "[whispers]-[yells]"
+
+ if(message_mods[SHOUT_MODE] == MODE_YELL)
+ return selector.get_saymod(SAY_MOD_YELL) || verb_yell
+
+ return selector.get_saymod(SAY_MOD_EXCLAIM) || verb_exclaim
+
+ if(message_mods[MODE_SING])
+ return selector.get_saymod(SAY_MOD_SING) || verb_sing
+
+ if(message_mods[WHISPER_MODE])
+ var/whispers = selector.get_saymod(SAY_MOD_WHISPER) || verb_whisper
+ if(message_mods[WHISPER_MODE] == MODE_WHISPER_CRIT && !HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE))
+ return"[whispers] in [p_their()] last breath"
+
+ return whispers
+
+ if(copytext_char(input, -1) == "?")
+ return selector.get_saymod(SAY_MOD_ASK) || verb_ask
+
+ return selector.get_saymod(SAY_MOD_DEFAULT) || get_default_say_verb() || verb_say
+
+/// Holder for managing saymod priorities and selection
+/datum/saymod_selector
+ var/list/say_mods
+
+/**
+ * Add a saymod to the selection pool
+ *
+ * * saymod_type - The type of saymod to add (e.g. SAY_MOD_ASK, SAY_MOD_DEFAULT, etc.)
+ * * new_verb - The verb to add to the pool (e.g. "says", "whispers", etc.)
+ * * priority - The priority of the saymod. If two saymods have the same priority, one will be chosen at random.
+ */
+/datum/saymod_selector/proc/add_saymod(saymod_type, new_verb, priority)
+ say_mods ||= alist()
+ say_mods[saymod_type] = alist()
+ say_mods[saymod_type][priority] ||= list()
+ say_mods[saymod_type][priority] |= new_verb
+
+/**
+ * Return a saymod from the selection pool, based on priority and randomness
+ *
+ * * saymod_type - The type of saymod to retrieve (e.g. SAY_MOD_ASK, SAY_MOD_DEFAULT, etc.)
+ *
+ * Returns a string, or null if there was no saymod of that type in the pool.
+ */
+/datum/saymod_selector/proc/get_saymod(saymod_type)
+ var/highest_priority = -INFINITY
+ var/list/selected_pool
+ for(var/priority, verbs in say_mods?[saymod_type])
+ if(priority < highest_priority)
+ continue
+ highest_priority = priority
+ selected_pool = verbs
+
+ if(length(selected_pool))
+ . = pick(selected_pool)
+
+ // All done, clean up
+ if(!QDELING(src))
+ qdel(src)
/**
* Gets the say verb we default to if no special verb is chosen.
@@ -232,9 +297,6 @@ GLOBAL_LIST_INIT(freqtospan, list(
SEND_SIGNAL(src, COMSIG_MOVABLE_SAY_QUOTE, args)
- if(copytext_char(input, -2) == "!!")
- spans |= SPAN_YELL
-
/* all inputs should be fully figured out past this point */
var/processed_input = say_emphasis(input) //This MUST be done first so that we don't get clipped by spans
diff --git a/code/modules/hallucination/fake_chat.dm b/code/modules/hallucination/fake_chat.dm
index 3f2d39e46610..c965344bcf50 100644
--- a/code/modules/hallucination/fake_chat.dm
+++ b/code/modules/hallucination/fake_chat.dm
@@ -118,8 +118,15 @@
if(plus_runechat)
hallucinator.create_chat_message(speaker, understood_language, chosen, spans)
+ var/list/message_mods = list()
+ if(is_radio)
+ // melbert todo - This is goofy but I'll fix it laterrrrr
+ var/obj/item/radio/headset/fake_headset = new()
+ message_mods[SAY_RADIO_ICON] = "[icon2html(fake_headset.get_radio_icon(), hallucinator)]"
+ qdel(fake_headset)
+
// And actually show them the message, for real.
- var/message = hallucinator.compose_message(speaker, understood_language, chosen, is_radio ? "[FREQ_COMMON]" : null, spans, visible_name = TRUE)
+ var/message = hallucinator.compose_message(speaker, understood_language, chosen, is_radio ? "[FREQ_COMMON]" : null, spans, message_mods, TRUE)
to_chat(hallucinator, message)
// Then clean up.
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 2327da3c1f9a..a245ba179755 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -66,6 +66,7 @@
message_mime = "acts out a scream!"
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
vary = TRUE
+ emote_range = DEFAULT_MESSAGE_RANGE * 2
/datum/emote/living/carbon/human/scream/get_sound(mob/living/carbon/human/user)
if(!istype(user))
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index bb2ab786df4b..918876b5f8c9 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -24,10 +24,9 @@
/mob/living/carbon/human/get_default_say_verb()
var/obj/item/organ/tongue/tongue = get_organ_slot(ORGAN_SLOT_TONGUE)
if(isnull(tongue))
- if(HAS_TRAIT(src, TRAIT_SIGN_LANG))
- return "signs"
- return "gurgles"
- return tongue.temp_say_mod || tongue.say_mod || ..()
+ return HAS_TRAIT(src, TRAIT_SIGN_LANG) ? "signs" : "gurgles"
+
+ return ..()
/mob/living/carbon/human/get_voice(add_id_name = FALSE)
if(HAS_TRAIT(src, TRAIT_UNKNOWN_VOICE))
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index ea2850f35dab..6ccc2330b62c 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -381,6 +381,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.
+ emote_range = DEFAULT_MESSAGE_RANGE * 2
/datum/emote/living/scream/select_message_type(mob/user, message, intentional)
. = ..()
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index 0fe880634764..29629c32b99d 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -211,6 +211,13 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
succumb()
return
+ var/static/regex/shout_regex = regex(@"\w+\s*(!+)\s*$\s*", "i")
+ if(!message_mods[SHOUT_MODE] && shout_regex.Find(message))
+ message_mods[SHOUT_MODE] = length_char(shout_regex.group[1]) > 1 ? MODE_YELL : MODE_SHOUT
+ message_range = floor(message_range * (message_mods[SHOUT_MODE] ? 2 : 1.5))
+ if(message_mods[SHOUT_MODE] == MODE_YELL)
+ spans |= SPAN_YELL
+
//Get which verb is prefixed to the message before radio but after most modifications
message_mods[SAY_MOD_VERB] = say_mod(message, message_mods)
@@ -321,20 +328,6 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
if(outside_dist > 0)
raw_message = stars(raw_message)
- if(client?.prefs?.read_preference(/datum/preference/toggle/distance_text_shrinking))
- // Based on raw distance, change the font size to indicate further speech
- var/span_to_add = null
- switch(raw_dist)
- if(5)
- span_to_add = "distant_t1"
- if(6)
- span_to_add = "distant_t2"
- if(7 to INFINITY)
- span_to_add = "distant_t3"
- if(span_to_add)
- spans = spans?.Copy() || list() // avoid mutating the list for other hearers
- spans |= span_to_add
-
// we need to send this signal before compose_message() is used since other signals need to modify
// the raw_message first. After the raw_message is passed through the various signals, it's ready to be formatted
// by compose_message() to be displayed in chat boxes for to_chat or runechat
@@ -559,32 +552,6 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
return NONE
-/mob/living/say_mod(input, list/message_mods = list())
- if(message_mods[WHISPER_MODE] == MODE_WHISPER)
- . = verb_whisper
- else if(message_mods[WHISPER_MODE] == MODE_WHISPER_CRIT && !HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE))
- . = "[verb_whisper] in [p_their()] last breath"
- else if(message_mods[MODE_SING])
- . = verb_sing
- // Any subtype of slurring in our status effects make us "slur"
- else if(locate(/datum/status_effect/speech/slurring) in status_effects)
- if (HAS_TRAIT(src, TRAIT_SIGN_LANG))
- . = "loosely signs"
- else
- . = "slurs"
- else if(has_status_effect(/datum/status_effect/speech/stutter))
- if(HAS_TRAIT(src, TRAIT_SIGN_LANG))
- . = "shakily signs"
- else
- . = "stammers"
- else if(has_status_effect(/datum/status_effect/speech/stutter/derpspeech))
- if(HAS_TRAIT(src, TRAIT_SIGN_LANG))
- . = "incoherently signs"
- else
- . = "gibbers"
- else
- . = ..()
-
/**
* Living level whisper.
*
diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm
index 221586478232..1910c669ae94 100644
--- a/code/modules/mob/living/silicon/robot/emote.dm
+++ b/code/modules/mob/living/silicon/robot/emote.dm
@@ -63,6 +63,7 @@
message = "blares an alarm!"
emote_type = EMOTE_AUDIBLE
sound = 'sound/machines/warning-buzzer.ogg'
+ emote_range = DEFAULT_MESSAGE_RANGE * 2
/datum/emote/silicon/slowclap
key = "slowclap"
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 74f9a8b1c20c..26ffa76dfdf7 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -251,6 +251,27 @@
to_chat(src, msg, avoid_highlighting = avoid_highlighting)
return .
+/// Returns a span based on distance from this atom to something else
+/atom/proc/get_distance_based_span(distance)
+ switch(distance)
+ if(5, 6)
+ return "distant_t1"
+ if(7, 8)
+ return "distant_t2"
+ if(9 to INFINITY)
+ return "distant_t3"
+
+ return null
+
+/mob/get_distance_based_span(distance)
+ if(client?.prefs?.read_preference(/datum/preference/toggle/distance_text_shrinking))
+ return ..()
+
+ return null
+
+/mob/dead/get_distance_based_span(distance)
+ return null
+
/**
* Generate a visible message from this atom
*
@@ -285,7 +306,7 @@
var/raw_msg = message
if(visible_message_flags & EMOTE_MESSAGE)
- message = "[src] [message]"
+ message = span_emote("[src] [message]")
for(var/mob/M in hearers)
if(!M.client)
@@ -293,6 +314,7 @@
//This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
+ var/blind_msg = blind_message
var/msg_type = MSG_VISUAL
if(M.see_invisible < invisibility)//if src is invisible to M
@@ -311,8 +333,9 @@
if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, visible_message_flags) && !M.is_blind())
M.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
- M.show_message(msg, msg_type, blind_message, MSG_AUDIBLE)
+ var/distance_span = M.get_distance_based_span(get_dist(src, M) - 4) // -4 effective distance
+ M.show_message(CONDITIONAL_SPAN(distance_span, msg), msg_type, CONDITIONAL_SPAN(distance_span, blind_msg), MSG_AUDIBLE)
///Adds the functionality to self_message.
/mob/visible_message(message, self_message, blind_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, visible_message_flags = NONE)
@@ -322,7 +345,7 @@
var/raw_self_message = self_message
var/self_runechat = FALSE
if(visible_message_flags & EMOTE_MESSAGE)
- self_message = "[src] [self_message]" // May make more sense as "You do x"
+ self_message = span_emote("[src] [self_message]") // May make more sense as "You do x"
if(visible_message_flags & ALWAYS_SHOW_SELF_MESSAGE)
to_chat(src, self_message)
@@ -352,11 +375,17 @@
hearers -= src
var/raw_msg = message
if(audible_message_flags & EMOTE_MESSAGE)
- message = "[src] [message]"
+ message = span_emote("[src] [message]")
for(var/mob/M in hearers)
+ if(!M.client)
+ continue
+
if(audible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, audible_message_flags) && M.can_hear())
M.create_chat_message(src, raw_message = raw_msg, runechat_flags = audible_message_flags)
- M.show_message(message, MSG_AUDIBLE, deaf_message, MSG_VISUAL)
+
+ var/distance_span = M.get_distance_based_span(get_dist(src, M) - 2) // -2 effective distance
+
+ M.show_message(CONDITIONAL_SPAN(distance_span, message), MSG_AUDIBLE, CONDITIONAL_SPAN(distance_span, deaf_message), MSG_VISUAL)
/**
* Show a message to all mobs in earshot of this one
@@ -376,7 +405,7 @@
var/raw_self_message = self_message
var/self_runechat = FALSE
if(audible_message_flags & EMOTE_MESSAGE)
- self_message = "[src] [self_message]"
+ self_message = span_emote("[src] [self_message]")
if(audible_message_flags & ALWAYS_SHOW_SELF_MESSAGE)
to_chat(src, self_message)
self_runechat = TRUE
diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm
index 8086fff7dfe4..03ddcd65fe61 100644
--- a/code/modules/surgery/organs/internal/tongue/_tongue.dm
+++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm
@@ -24,11 +24,8 @@
* (such as no accent, hissing, or whatever)
*/
var/list/languages_native
- ///changes the verbage of how you speak. (Permille -> says <-, "I just used a verb!")
- ///i hate to say it, but because of sign language, this may have to be a component. and we may have to do some insane shit like putting a component on a component
+ /// Changes the verbage of how you speak. (Permille -> says <-, "I just used a verb!")
var/say_mod = "says"
- ///for temporary overrides of the above variable.
- var/temp_say_mod = ""
/// Whether the owner of this tongue can taste anything. Being set to FALSE will mean no taste feedback will be provided.
var/sense_of_taste = TRUE
@@ -128,6 +125,7 @@
if(modifies_speech)
RegisterSignal(receiver, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ RegisterSignal(receiver, COMSIG_MOVABLE_SAY_MOD, PROC_REF(handle_saymod))
receiver.voice_filter = voice_filter
/* This could be slightly simpler, by making the removal of the
* NO_TONGUE_TRAIT conditional on the tongue's `sense_of_taste`, but
@@ -140,8 +138,8 @@
/obj/item/organ/tongue/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags)
. = ..()
- temp_say_mod = ""
UnregisterSignal(organ_owner, COMSIG_MOB_SAY)
+ UnregisterSignal(organ_owner, COMSIG_MOVABLE_SAY_MOD)
REMOVE_TRAIT(organ_owner, TRAIT_SPEAKS_CLEARLY, SPEAKING_FROM_TONGUE)
REMOVE_TRAIT(organ_owner, TRAIT_AGEUSIA, ORGAN_TRAIT)
// Carbons by default start with NO_TONGUE_TRAIT caused TRAIT_AGEUSIA
@@ -180,6 +178,10 @@
// No effect
return ""
+/obj/item/organ/tongue/proc/handle_saymod(datum/source, datum/saymod_selector/selector)
+ SIGNAL_HANDLER
+ selector.add_saymod(SAY_MOD_DEFAULT, say_mod, SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/lizard
name = "forked tongue"
desc = "A thin and long muscle typically found in reptilian races, apparently moonlights as a nose."
@@ -200,6 +202,16 @@
if(prob(10))
draw_length += 2
+/obj/item/organ/tongue/lizard/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_ASK, "hisses", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_EXCLAIM, "growls", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_SING, "chuffs", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER, "hisses", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "growls", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "bellows", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "roars", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/lizard/modify_speech(datum/source, list/speech_args)
var/static/regex/lizard_hiss = new("s+", "g")
var/static/regex/lizard_hiSS = new("S+", "g")
@@ -576,6 +588,15 @@ GLOBAL_LIST_INIT(english_to_zombie, list())
/obj/item/organ/tongue/robot/modify_speech(datum/source, list/speech_args)
speech_args[SPEECH_SPANS] |= SPAN_ROBOT
+/obj/item/organ/tongue/robot/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_ASK, "queries", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_EXCLAIM, "blares", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_SING, "synthesizes", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER, "whirrs", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "states", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "alarms", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/snail
name = "radula"
desc = "A minutely toothed, chitious ribbon, which as a side effect, makes all snails talk IINNCCRREEDDIIBBLLYY SSLLOOWWLLYY."
@@ -610,6 +631,15 @@ GLOBAL_LIST_INIT(english_to_zombie, list())
/obj/item/organ/tongue/ethereal/get_possible_languages()
return ..() + /datum/language/voltaic
+/obj/item/organ/tongue/ethereal/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_ASK, "crackles", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_EXCLAIM, "crackles", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_SING, "thrums", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER, "hums", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "crackles", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "buzzes", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/cat
name = "felinid tongue"
desc = "A fleshy muscle mostly used for meowing."
@@ -618,6 +648,14 @@ GLOBAL_LIST_INIT(english_to_zombie, list())
disliked_foodtypes = GROSS | CLOTH | RAW
organ_traits = list(TRAIT_FISH_EATER)
+/obj/item/organ/tongue/cat/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_ASK, "mrowls", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_EXCLAIM, "hisses", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER, "purrs", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "hisses", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "yowls", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/jelly
name = "jelly tongue"
desc = "Ah... That's not the sound I expected it to make. Sounds like a Space Autumn Bird."
@@ -639,6 +677,12 @@ GLOBAL_LIST_INIT(english_to_zombie, list())
liked_foodtypes = MEAT | FRUIT | BUGS
disliked_foodtypes = CLOTH
+/obj/item/organ/tongue/monkey/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_EXCLAIM, "screeches", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "screeches", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "roars", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/moth
name = "moth tongue"
desc = "Moths don't have tongues. Someone get god on the phone, tell them I'm not happy."
@@ -647,6 +691,14 @@ GLOBAL_LIST_INIT(english_to_zombie, list())
disliked_foodtypes = FRUIT | GROSS | BUGS | GORE
toxic_foodtypes = MEAT | RAW | SEAFOOD
+/obj/item/organ/tongue/moth/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_ASK, "flutters", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_EXCLAIM, "flutters", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER, "squeaks", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "flutters", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "flutters", SAY_MOD_PRIORITY_DEFAULT)
+
/obj/item/organ/tongue/zombie
name = "rotting tongue"
desc = "Makes you speak like you're at the dentist and you just absolutely refuse to spit because you forgot to mention you were allergic to space shellfish."
diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm
index 956fcedc92fd..b0d72f07f4b1 100644
--- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm
+++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android.dm
@@ -94,6 +94,8 @@
set_mutant_organ(ORGAN_SLOT_EYES, /obj/item/organ/eyes/robotic/basic/moth, gained_species)
if(SPECIES_FELINE)
set_mutant_organ(ORGAN_SLOT_EARS, /obj/item/organ/ears/cat/cybernetic, gained_species)
+ if(SPECIES_LIZARD)
+ set_mutant_organ(ORGAN_SLOT_TONGUE, /obj/item/organ/tongue/robot/android/lizard, gained_species)
RegisterSignal(gained_species, COMSIG_HUMAN_ON_HANDLE_BLOOD, PROC_REF(handle_blood))
RegisterSignal(gained_species, COMSIG_CARBON_HEARTBEAT, PROC_REF(handle_heartbeat))
diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm
index 83ab1517c632..2f006b9f2276 100644
--- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm
+++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/android_tongue.dm
@@ -5,6 +5,9 @@
toxic_foodtypes = NONE
organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED)
+/obj/item/organ/tongue/robot/android/lizard
+ name = "tiziran android voicebox"
+
/obj/item/organ/tongue/robot/synth
name = "synth tongue"
organ_traits = list(TRAIT_SILICON_EMOTES_ALLOWED)
@@ -25,6 +28,12 @@
fake_tongue.handle_speech(source, speech_args)
+/obj/item/organ/tongue/robot/synth/handle_saymod(datum/source, datum/saymod_selector/selector)
+ if(isnull(fake_tongue))
+ return ..()
+
+ fake_tongue.handle_saymod(source, selector)
+
/obj/item/organ/tongue/robot/synth/proc/disguise_tongue(obj/item/organ/tongue/tongue_type)
if(isnull(tongue_type))
return
diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm
index cd4c7b9b9204..22fcf8750542 100644
--- a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm
+++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/synth/synth.dm
@@ -73,6 +73,7 @@
qdel(synth.GetComponent(/datum/component/ion_storm_randomization))
drop_disguise(synth)
UnregisterSignal(synth, COMSIG_CARBON_LIMB_DAMAGED)
+ UnregisterSignal(synth, COMSIG_MOVABLE_SAY_MOD)
for(var/obj/item/bodypart/limb as anything in synth.get_bodyparts())
if(initial(limb.limb_id) == BODYPART_ID_SYNTH)
@@ -240,16 +241,14 @@
limb_gained(synth, limb, update = FALSE)
changed_limbs += limb
if(istype(limb, /obj/item/bodypart/head))
- var/obj/item/organ/tongue/tongue = synth.get_organ_slot(ORGAN_SLOT_TONGUE)
- if(tongue?.temp_say_mod == "whirrs")
- tongue.temp_say_mod = null
+ UnregisterSignal(synth, COMSIG_MOVABLE_SAY_MOD)
+
else
if(below_threshold)
limb_lost(synth, limb, update = FALSE)
changed_limbs += limb
if(istype(limb, /obj/item/bodypart/head))
- var/obj/item/organ/tongue/tongue = synth.get_organ_slot(ORGAN_SLOT_TONGUE)
- tongue?.temp_say_mod = "whirrs"
+ RegisterSignal(synth, COMSIG_MOVABLE_SAY_MOD, PROC_REF(handle_saymod))
var/num_changes = length(changed_limbs)
if(num_changes > 0)
@@ -259,6 +258,10 @@
synth.visible_message(span_warning("[synth]'s [changed_limbs[1].plaintext_zone] changes appearance!"))
synth.update_body_parts(TRUE)
+/datum/species/android/synth/proc/handle_saymod(datum/source, datum/saymod_selector/selector)
+ SIGNAL_HANDLER
+ selector.add_saymod(SAY_MOD_DEFAULT, "whirrs", SAY_MOD_PRIORITY_MEDIUM)
+
/// Like change appearance, but passing it a bodypart will change the appearance to that of the bodypart.
/obj/item/bodypart/proc/change_appearance_into(obj/item/bodypart/other_part, update = TRUE)
draw_color = initial(other_part.draw_color)
diff --git a/maplestation_modules/code/modules/mob/living/speech_and_radio_sounds.dm b/maplestation_modules/code/modules/mob/living/speech_and_radio_sounds.dm
index 78e52c01c108..4acd519f04d6 100644
--- a/maplestation_modules/code/modules/mob/living/speech_and_radio_sounds.dm
+++ b/maplestation_modules/code/modules/mob/living/speech_and_radio_sounds.dm
@@ -141,12 +141,15 @@
if(message_mods[WHISPER_MODE])
speech_sound_vol = max(speech_sound_vol - 10, 10)
speech_sound_rangemod = -15 // 2 range
+ if(message_mods[SHOUT_MODE])
+ speech_sound_vol = min(speech_sound_vol + ((message_mods[SHOUT_MODE] == MODE_YELL) ? 20 : 10), 100)
+ speech_sound_rangemod = (message_mods[SHOUT_MODE] == MODE_YELL) ? -5 : -7 // 12 / 10 range
var/sound/the_sound = sound(picked_sound)
the_sound.pitch = speech_sound_pitch_modifier
the_sound.frequency = sound_frequency * speech_sound_frequency_modifier
if(is_mouth_covered())
- speech_sound_vol *= 1.5
+ the_sound.frequency *= 0.75
playsound(
source = src,
diff --git a/maplestation_modules/code/modules/surgery/organs/tongue.dm b/maplestation_modules/code/modules/surgery/organs/tongue.dm
index 38dbfa4e3036..43683cc72f2d 100644
--- a/maplestation_modules/code/modules/surgery/organs/tongue.dm
+++ b/maplestation_modules/code/modules/surgery/organs/tongue.dm
@@ -59,6 +59,13 @@
disliked_foodtypes = DAIRY | CLOTH | GROSS
toxic_foodtypes = SUGAR // chocolate is toxic to birds.
+/obj/item/organ/tongue/ornithid/handle_saymod(datum/source, datum/saymod_selector/selector)
+ . = ..()
+ selector.add_saymod(SAY_MOD_EXCLAIM, "caws", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_SING, "tweets", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_WHISPER_YELL, "croons", SAY_MOD_PRIORITY_DEFAULT)
+ selector.add_saymod(SAY_MOD_YELL, "caws", SAY_MOD_PRIORITY_DEFAULT)
+
// High Draconic for lizard tongues
/obj/item/organ/tongue/lizard
languages_native = list(/datum/language/draconic, /datum/language/impdraconic)