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)