From 4962a86b9594fbaabbef47606dd7224006c67d0f Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:52:28 -0500 Subject: [PATCH] CSL / Mutual understanding changes (#96774) ## About The Pull Request 1. Redid the formula for calculating mutual language understanding Old: `base + (10 * (1 - (min(commonness, 1250) / 500)))`, or in other words `base + 10% to -15%` New: `base + max(20 - (commonness / 1000) * 20, -5)`, or in other words `base + 20% to -5%` 2. CSL strength preference has been changed Old: `"90%", "75%", "50%", "33%", "25%", "10%"` New: `"75%", "50%", "25%"` 3. If you understand a language partially, the icon in chat is differentiated slightly image (Icon subject to change, it's just a `?` spliced with the icon rn) 4. CSL can now be taken by humans, doing so grants you gal-uncommon 5. Fixed a bug where contractions, due to being encoded, would be treated as lower frequency / rarer and thus be more likely to be translated ## Why It's Good For The Game 1. The current formula was a bit too harsh and resulted in more words being translated than you'd expect. The altered formula should make it less persistent. 2. These values were tweaked according to the new formula. If I left the 90% value it'd mean like every word would get translated and that defeats the point. 3. Makes things a bit clearer when it comes to partial understanding. 4. Requested, sounds like fun so why not. 5. Bugfix. ## Changelog :cl: Melbert fix: Fixed a bug where contractions were treated as rarer words than they should be for the sake of partial language understanding balance: Tweaked the formula of partial language understanding: Less common words are penalized less, more common words are rewarded more. In other words more words will be understood on average. balance: Common Second Language numbers were tweaked according to the new formula. Now "75%", "50%", "25%". balance: Humans can now take Common Second Language. Doing so also grants Galactic Uncommon. add: Partially understood sentences now get an altered language icon in chat and runechat, indicating that only some of the words were translated. /:cl: --- code/__DEFINES/language.dm | 21 +++++++-- code/datums/chatmessage.dm | 45 +++++++++++-------- code/datums/quirks/negative_quirks/csl.dm | 15 ++++--- code/game/atoms_movable.dm | 8 ++++ code/game/say.dm | 7 ++- code/modules/asset_cache/assets/chat.dm | 16 ++++--- code/modules/client/preferences/language.dm | 4 +- code/modules/language/_language.dm | 30 ++++++------- code/modules/language/_language_holder.dm | 8 ++++ code/modules/language/carptongue.dm | 2 +- code/modules/language/codespeak.dm | 2 +- code/modules/language/common.dm | 2 +- code/modules/language/draconic.dm | 2 +- code/modules/language/drone.dm | 2 +- code/modules/language/machine.dm | 2 +- code/modules/language/spinwarder.dm | 2 +- code/modules/language/uncommon.dm | 2 +- code/modules/mob/living/carbon/carbon_say.dm | 2 +- icons/ui/chat/language.dmi | Bin 5437 -> 5763 bytes 19 files changed, 110 insertions(+), 62 deletions(-) diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index 937fc987aa6..1ae7d898203 100644 --- a/code/__DEFINES/language.dm +++ b/code/__DEFINES/language.dm @@ -1,7 +1,20 @@ -#define NO_STUTTER 1 -#define TONGUELESS_SPEECH 2 -#define LANGUAGE_HIDE_ICON_IF_UNDERSTOOD 4 -#define LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD 8 +// Language flags +/// Language is never stuttered +#define LANGUAGE_NO_STUTTER (1<<0) +/// Language is speakable without a tongue +#define LANGUAGE_TONGUELESS_SPEECH (1<<1) +/// Language icon is hidden if the language is understood +#define LANGUAGE_HIDE_ICON_IF_UNDERSTOOD (1<<2) +/// Language icon is hidden if the language is not understood +#define LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD (1<<3) + +// Returns from display_icon_type +/// Display no icon for this language +#define DISPLAY_LANGUAGE_ICON_NONE 0 +/// Display the full icon for this language +#define DISPLAY_LANGUAGE_ICON_FULL 1 +/// Display a blended icon for this language, indicating partial understanding +#define DISPLAY_LANGUAGE_ICON_PARTIAL 2 // LANGUAGE SOURCE DEFINES /// For use in full removal only. diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index cf555170b68..dce1ca6791d 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -64,7 +64,7 @@ * * extra_classes - Extra classes to apply to the span that holds the text * * lifespan - The lifespan of the message in deciseconds */ -/datum/chatmessage/New(text, atom/target, mob/owner, datum/language/language, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN) +/datum/chatmessage/New(text, atom/target, mob/owner, datum/language/language, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN, list/message_mods) . = ..() if (!istype(target)) CRASH("Invalid target given for chatmessage") @@ -72,7 +72,7 @@ stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner") qdel(src) return - INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan) + INVOKE_ASYNC(src, PROC_REF(generate_image), text, target, owner, language, extra_classes, lifespan, message_mods) /datum/chatmessage/Destroy() if (!QDELING(owned_by)) @@ -109,8 +109,9 @@ * * language - The language this message was spoken in * * extra_classes - Extra classes to apply to the span that holds the text * * lifespan - The lifespan of the message in deciseconds + * * message_mods - All mods from whatever message triggered this (if applicable) */ -/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, datum/language/language, list/extra_classes, lifespan) +/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, datum/language/language, list/extra_classes, lifespan, list/message_mods) /// Cached icons to show what language the user is speaking var/static/list/language_icons @@ -171,12 +172,16 @@ // Append language icon if the language uses one var/datum/language/language_instance = GLOB.language_datum_instances[language] - if (language_instance?.display_icon(owner)) - var/icon/language_icon = LAZYACCESS(language_icons, language) + var/language_icon_type = language_instance?.display_icon_type(owner, message_mods) || DISPLAY_LANGUAGE_ICON_NONE + if(language_icon_type != DISPLAY_LANGUAGE_ICON_NONE) + var/language_icon_key = "[language][language_icon_type == DISPLAY_LANGUAGE_ICON_PARTIAL ? "-partial" : ""]" + var/icon/language_icon = LAZYACCESS(language_icons, language_icon_key) if (isnull(language_icon)) language_icon = icon(language_instance.icon, icon_state = language_instance.icon_state) + if(language_icon_type == DISPLAY_LANGUAGE_ICON_PARTIAL) + language_icon.Blend(icon('icons/ui/chat/language.dmi', "unknown"), ICON_OVERLAY) language_icon.Scale(CHAT_MESSAGE_ICON_SIZE, CHAT_MESSAGE_ICON_SIZE) - LAZYSET(language_icons, language, language_icon) + LAZYSET(language_icons, language_icon_key, language_icon) LAZYADD(prefixes, "\icon[language_icon]") text = "[prefixes?.Join(" ")][text]" @@ -319,30 +324,34 @@ * * raw_message - The text content of the message * * spans - Additional classes to be added to the message */ -/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE) +/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE, list/message_mods) if(SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(speaker, TRAIT_BYPASS_MEASURES)) return if(HAS_TRAIT(speaker, TRAIT_RUNECHAT_HIDDEN)) return // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us - spans = spans ? spans.Copy() : list() + var/list/classes = (runechat_flags & EMOTE_MESSAGE) ? list("emote", "italics") : LAZYCOPY(spans) // Check for virtual speakers (aka hearing a message through a radio) - var/atom/movable/originalSpeaker = speaker - if (istype(speaker, /atom/movable/virtualspeaker)) - var/atom/movable/virtualspeaker/v = speaker - speaker = v.source - spans |= "virtual-speaker" + var/atom/movable/original_speaker = speaker + if (!(runechat_flags & EMOTE_MESSAGE) && istype(speaker, /atom/movable/virtualspeaker)) + var/atom/movable/virtualspeaker/virtual_speaker = speaker + speaker = virtual_speaker.source + classes |= "virtual-speaker" // Ignore virtual speaker (most often radio messages) from ourselves - if (originalSpeaker != src && speaker == src) + if (original_speaker != src && speaker == src) return // Display visual above source - if(runechat_flags & EMOTE_MESSAGE) - new /datum/chatmessage(raw_message, speaker, src, message_language, list("emote", "italics")) - else - new /datum/chatmessage(raw_message, speaker, src, message_language, spans) + new /datum/chatmessage( + text = raw_message, + target = speaker, + owner = src, + language = message_language, + extra_classes = classes, + message_mods = message_mods, + ) #undef CHAT_LAYER_MAX_Z #undef CHAT_LAYER_Z_STEP diff --git a/code/datums/quirks/negative_quirks/csl.dm b/code/datums/quirks/negative_quirks/csl.dm index 68a8f58be68..752a7d2dc31 100644 --- a/code/datums/quirks/negative_quirks/csl.dm +++ b/code/datums/quirks/negative_quirks/csl.dm @@ -10,13 +10,14 @@ lose_text = span_notice("Common starts to click for you.") medical_record_text = "Patient is CSL." /// What language typepath is our primary language? - var/native_language + VAR_PRIVATE/native_language /datum/quirk/csl/add(client/client_source) - if(iscarbon(quirk_holder)) - quirk_holder.remove_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) - else - quirk_holder.remove_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_ATOM) + quirk_holder.remove_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + quirk_holder.remove_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_ATOM) + if(length(quirk_holder.get_understood_languages()) < 1) + quirk_holder.grant_language(/datum/language/uncommon, SPOKEN_LANGUAGE|UNDERSTOOD_LANGUAGE, LANGUAGE_QUIRK) + quirk_holder.grant_partial_language(/datum/language/common, text2num(client_source?.prefs?.read_preference(/datum/preference/choiced/csl_strength)) || 90, type) RegisterSignal(quirk_holder, COMSIG_SPECIES_GAIN, PROC_REF(reremove_common)) RegisterSignal(quirk_holder, COMSIG_MOB_SAY, PROC_REF(translate_parts)) @@ -30,6 +31,7 @@ return quirk_holder.remove_partial_language(/datum/language/common, type) + quirk_holder.remove_language(/datum/language/uncommon, SPOKEN_LANGUAGE|UNDERSTOOD_LANGUAGE, LANGUAGE_QUIRK) var/mob/living/carbon/carbon_quirk_holder = quirk_holder if(istype(carbon_quirk_holder) && carbon_quirk_holder.dna.species) // only give back common if they're a species that should speak it @@ -78,10 +80,9 @@ if(say_args[SPEECH_FORCED] || isnull(native_language) || quirk_holder.mob_mood?.sanity > 75) return - // init this list if nothing else has - LAZYINITLIST(say_args[SPEECH_MODS][LANGUAGE_MUTUAL_BONUS]) // force speak language, add mutual bonuses so everyone else can understand say_args[SPEECH_LANGUAGE] = native_language + say_args[SPEECH_MODS][LANGUAGE_MUTUAL_BONUS] ||= list() say_args[SPEECH_MODS][LANGUAGE_MUTUAL_BONUS][native_language] = max(round(8 * sqrt(quirk_holder.mob_mood?.sanity), 5), say_args[SPEECH_MODS][LANGUAGE_MUTUAL_BONUS][native_language]) /datum/quirk_constant_data/csl diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 5475913e1c7..2ffa7e29ac9 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1598,6 +1598,10 @@ /atom/movable/proc/has_language(language, flags_to_check) return get_language_holder().has_language(language, flags_to_check) +/// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. +/atom/movable/proc/has_partial_language(language) + return get_language_holder().has_partial_language(language) + /// Checks if atom can speak the language. /atom/movable/proc/can_speak_language(language) return get_language_holder().can_speak_language(language) @@ -1623,6 +1627,10 @@ /atom/movable/proc/get_random_spoken_language() return get_language_holder().get_random_spoken_language() +/// Gets a list of all understood languages, excluding any blocked languages +/atom/movable/proc/get_understood_languages() as /list + return get_language_holder().get_understood_languages() || list() + /// Copies all languages into the supplied atom/language holder. Source should be overridden when you /// do not want the language overwritten by later atom updates or want to avoid blocked languages. /atom/movable/proc/copy_languages(datum/language_holder/from_holder, source_override) diff --git a/code/game/say.dm b/code/game/say.dm index c707f90f9c9..51f48cc8bca 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -155,6 +155,7 @@ GLOBAL_LIST_INIT(freqtospan, list( listened += hearing_movable do_tts_message(tts_message_to_use, message_language, message_mods, tts_filter, listened) + /atom/movable/proc/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, list/spans, list/message_mods = list(), visible_name = FALSE) //This proc uses [] because it is faster than continually appending strings. Thanks BYOND. //Basic span @@ -174,8 +175,10 @@ GLOBAL_LIST_INIT(freqtospan, list( var/languageicon = "" if(!message_mods[MODE_CUSTOM_SAY_ERASE_INPUT]) var/datum/language/dialect = GLOB.language_datum_instances[message_language] - if(istype(dialect) && dialect.display_icon(src)) - languageicon = "[dialect.get_icon()] " + var/dialect_icon_type = dialect?.display_icon_type(src, message_mods) || DISPLAY_LANGUAGE_ICON_NONE + if(dialect_icon_type != DISPLAY_LANGUAGE_ICON_NONE) + var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/chat) + languageicon = sheet.icon_tag("language-[dialect.icon_state][dialect_icon_type == DISPLAY_LANGUAGE_ICON_PARTIAL ? "-partial" : ""]") + " " // The actual message part. var/messagepart = speaker.generate_messagepart(raw_message, spans, message_mods) diff --git a/code/modules/asset_cache/assets/chat.dm b/code/modules/asset_cache/assets/chat.dm index 890f05afe39..b5b0e596bca 100644 --- a/code/modules/asset_cache/assets/chat.dm +++ b/code/modules/asset_cache/assets/chat.dm @@ -1,17 +1,23 @@ /datum/asset/spritesheet_batched/chat name = "chat" + /// Default file for language icons + var/default_language_file = 'icons/ui/chat/language.dmi' /datum/asset/spritesheet_batched/chat/create_spritesheets() insert_all_icons("emoji", EMOJI_SET) // pre-loading all lanugage icons also helps to avoid meta - insert_all_icons("language", 'icons/ui/chat/language.dmi') + insert_all_icons("language", default_language_file) // catch languages which are pulling icons from another file - for(var/datum/language/L as anything in subtypesof(/datum/language)) - var/icon = initial(L.icon) - if (icon != 'icons/ui/chat/language.dmi') - var/icon_state = initial(L.icon_state) + for(var/datum/language/lang_type as anything in subtypesof(/datum/language)) + var/icon = initial(lang_type.icon) + var/icon_state = initial(lang_type.icon_state) + if (icon != default_language_file) insert_icon("language-[icon_state]", uni_icon(icon, icon_state)) + var/datum/universal_icon/partial_icon = uni_icon(icon, icon_state) + partial_icon.blend_icon(uni_icon(default_language_file, "unknown"), ICON_OVERLAY) + insert_icon("language-[icon_state]-partial", partial_icon) + var/datum/universal_icon/byond_member = uni_icon('icons/ui/chat/member_content.dmi', "blag") byond_member.scale(16, 16) insert_icon("byond_member", byond_member) diff --git a/code/modules/client/preferences/language.dm b/code/modules/client/preferences/language.dm index c9f69c6e205..f822bdf323d 100644 --- a/code/modules/client/preferences/language.dm +++ b/code/modules/client/preferences/language.dm @@ -94,13 +94,13 @@ should_update_preview = FALSE /datum/preference/choiced/csl_strength/create_default_value() - return "90%" + return init_possible_values()[1] /datum/preference/choiced/csl_strength/is_accessible(datum/preferences/preferences) return ..() && (/datum/quirk/csl::name in preferences.all_quirks) /datum/preference/choiced/csl_strength/init_possible_values() - return list("90%", "75%", "50%", "33%", "25%", "10%") + return list("75%", "50%", "25%") /datum/preference/choiced/csl_strength/apply_to_human(mob/living/carbon/human/target, value) return diff --git a/code/modules/language/_language.dm b/code/modules/language/_language.dm index 65b107ee598..2c558e33f33 100644 --- a/code/modules/language/_language.dm +++ b/code/modules/language/_language.dm @@ -110,19 +110,19 @@ most_common_cache.Cut() last_sentence_cache.Cut() -/// Checks whether we should display the language icon to the passed hearer. -/datum/language/proc/display_icon(atom/movable/hearer) - var/understands = hearer.has_language(src.type) - if((flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD) && understands) - return FALSE - if((flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD) && !understands) - return FALSE - return TRUE +/// Returns what icon to display when speaking this language +/datum/language/proc/display_icon_type(atom/movable/hearer, list/message_mods) + if(hearer.has_language(type)) + if(flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD) + return DISPLAY_LANGUAGE_ICON_NONE -/// Returns the icon to display in the chat window when speaking this language. -/datum/language/proc/get_icon() - var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/chat) - return sheet.icon_tag("language-[icon_state]") + else + if(hearer.has_partial_language(type) || LAZYACCESSASSOC(message_mods, LANGUAGE_MUTUAL_BONUS, type)) + return DISPLAY_LANGUAGE_ICON_PARTIAL + if(flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD) + return DISPLAY_LANGUAGE_ICON_NONE + + return DISPLAY_LANGUAGE_ICON_FULL /// Simple helper for getting a default firstname lastname /datum/language/proc/default_name(gender = NEUTER) @@ -259,12 +259,12 @@ var/list/translated_index = list() for(var/word in splittext(input, " ")) var/translate_prob = mutual_languages?[type] || 0 - var/base_word = strip_outer_punctuation(word) + var/base_word = strip_outer_punctuation(html_decode(word)) if(translate_prob > 0) - // the probability of managing to understand a word is based on how common it is (+10%, -15%) + // the probability of managing to understand a word is based on how common it is (up to +20%, though -5% for very uncommon words) // 1000 words in the list, so words outside the list are just treated as "the 1250th most common word" var/commonness = GLOB.most_common_words_frequency[LOWER_TEXT(base_word)] || 1250 - translate_prob += (10 * (1 - (min(commonness, 1250) / 500))) + translate_prob += max(20 - (commonness / 1000) * 20, -5) if(prob(translate_prob)) scrambled_words += word translated_index += FALSE diff --git a/code/modules/language/_language_holder.dm b/code/modules/language/_language_holder.dm index c4207f40f07..c72e830d135 100644 --- a/code/modules/language/_language_holder.dm +++ b/code/modules/language/_language_holder.dm @@ -232,6 +232,10 @@ Key procs return language in langs_to_check +/// Checks if you have partial understanding of the language passed. +/datum/language_holder/proc/has_partial_language(language) + return LAZYACCESS(best_mutual_languages, language) + /// Checks if you can speak the language. Tongue limitations should be supplied as an argument. /datum/language_holder/proc/can_speak_language(language) var/can_speak_language_path = omnitongue || owner.could_speak_language(language) @@ -272,6 +276,10 @@ Key procs else return /datum/language/common +/// Returns a list of languages we understand +/datum/language_holder/proc/get_understood_languages() + return assoc_to_keys(understood_languages) - assoc_to_keys(blocked_understanding) + /// Opens a language menu reading from the language holder. /datum/language_holder/proc/open_language_menu(mob/user) if(!language_menu) diff --git a/code/modules/language/carptongue.dm b/code/modules/language/carptongue.dm index f3a376287ec..6215c618863 100644 --- a/code/modules/language/carptongue.dm +++ b/code/modules/language/carptongue.dm @@ -3,7 +3,7 @@ desc = "Various fishy sounds that might make sense to space carps." key = "c" icon_state = "fish" - flags = NO_STUTTER|TONGUELESS_SPEECH + flags = LANGUAGE_NO_STUTTER|LANGUAGE_TONGUELESS_SPEECH sentence_chance = 0 space_chance = 0 between_word_sentence_chance = 0 diff --git a/code/modules/language/codespeak.dm b/code/modules/language/codespeak.dm index 8cc8c1c460a..55af1b060a7 100644 --- a/code/modules/language/codespeak.dm +++ b/code/modules/language/codespeak.dm @@ -3,7 +3,7 @@ desc = "Syndicate operatives can use a series of codewords to convey complex information, while sounding like random concepts and drinks to anyone listening in." key = "t" default_priority = 0 - flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD + flags = LANGUAGE_TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD icon_state = "codespeak" always_use_default_namelist = TRUE // No syllables anyways diff --git a/code/modules/language/common.dm b/code/modules/language/common.dm index bf015a04e2f..7cd79359c44 100644 --- a/code/modules/language/common.dm +++ b/code/modules/language/common.dm @@ -3,7 +3,7 @@ name = "Galactic Common" desc = "The common galactic tongue." key = "0" - flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD + flags = LANGUAGE_TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD default_priority = 100 space_chance = 20 sentence_chance = 0 diff --git a/code/modules/language/draconic.dm b/code/modules/language/draconic.dm index 76cc7350f60..628587b7db9 100644 --- a/code/modules/language/draconic.dm +++ b/code/modules/language/draconic.dm @@ -2,7 +2,7 @@ name = "Draconic" desc = "The common language of lizard-people, composed of sibilant hisses and rattles." key = "o" - flags = TONGUELESS_SPEECH + flags = LANGUAGE_TONGUELESS_SPEECH space_chance = 12 sentence_chance = 0 between_word_sentence_chance = 10 diff --git a/code/modules/language/drone.dm b/code/modules/language/drone.dm index 373f504a135..3b220119e76 100644 --- a/code/modules/language/drone.dm +++ b/code/modules/language/drone.dm @@ -3,7 +3,7 @@ desc = "A heavily encoded damage control coordination stream, with special flags for hats." spans = list(SPAN_ROBOT) key = "d" - flags = NO_STUTTER + flags = LANGUAGE_NO_STUTTER syllables = list(".", "|") // ...|..||.||||.|.||.|.|.|||.||| space_chance = 0 diff --git a/code/modules/language/machine.dm b/code/modules/language/machine.dm index 63bec30c6f8..ab08d141237 100644 --- a/code/modules/language/machine.dm +++ b/code/modules/language/machine.dm @@ -3,7 +3,7 @@ desc = "An efficient language of encoded tones developed by synthetics and cyborgs." spans = list(SPAN_ROBOT) key = "6" - flags = NO_STUTTER + flags = LANGUAGE_NO_STUTTER syllables = list( "beep", "beep", "beep", "beep", "beep", "boop", "boop", "boop", "bop", "bop", "dee", "dee", "doo", "doo", "hiss", "hss", "buzz", diff --git a/code/modules/language/spinwarder.dm b/code/modules/language/spinwarder.dm index 8c3877f38c0..7e4f12f738c 100644 --- a/code/modules/language/spinwarder.dm +++ b/code/modules/language/spinwarder.dm @@ -10,7 +10,7 @@ between_word_space_chance = 75 additional_syllable_low = 0 additional_syllable_high = 0 - flags = TONGUELESS_SPEECH + flags = LANGUAGE_TONGUELESS_SPEECH syllables = list( "v", "od", "noy", "ned", "ele", "dn", "ey", "da", "ny", "et", "mes", "yat", "che", "bu", "pol", "ye", "eli", "nyy", "se", "god", "ya", "dom", "ash", "zav", diff --git a/code/modules/language/uncommon.dm b/code/modules/language/uncommon.dm index 43b878e8341..060f6c02cb1 100644 --- a/code/modules/language/uncommon.dm +++ b/code/modules/language/uncommon.dm @@ -2,7 +2,7 @@ name = "Galactic Uncommon" desc = "The second-most spoken Human language." key = "!" - flags = TONGUELESS_SPEECH + flags = LANGUAGE_TONGUELESS_SPEECH space_chance = 20 sentence_chance = 0 between_word_sentence_chance = 10 diff --git a/code/modules/mob/living/carbon/carbon_say.dm b/code/modules/mob/living/carbon/carbon_say.dm index e1dc3d27992..35ed43d8fde 100644 --- a/code/modules/mob/living/carbon/carbon_say.dm +++ b/code/modules/mob/living/carbon/carbon_say.dm @@ -12,4 +12,4 @@ // the tower of babel needs to bypass the tongue language restrictions without giving omnitongue return HAS_MIND_TRAIT(src, TRAIT_TOWER_OF_BABEL) || spoken_with.could_speak_language(language_path) - return initial(language_path.flags) & TONGUELESS_SPEECH + return initial(language_path.flags) & LANGUAGE_TONGUELESS_SPEECH diff --git a/icons/ui/chat/language.dmi b/icons/ui/chat/language.dmi index 7cb84ee3af9464502ef542b3b83986129dc35985..3a8a6148f44aefbe249735e89a72aff1bab6ae63 100644 GIT binary patch literal 5763 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 BNMHZ} literal 5437 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