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 8bbc22d1820..648ba4dc72a 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -138,4 +138,3 @@ #define COMSIG_MOVABLE_BUMP_PUSHED "movable_bump_pushed" /// Stop it from moving #define COMPONENT_NO_PUSH (1<<0) - diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index d80e0d1af2b..8c871c0d38a 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -5,6 +5,8 @@ #define RADIO_EXTENSION "department specific" #define RADIO_KEY "department specific key" #define LANGUAGE_EXTENSION "language specific" +/// Message mod which contains a list of bonus "mutual understanding" to allow arbitrary understanding of any speech +#define LANGUAGE_MUTUAL_BONUS "language mutual bonus" #define SAY_MOD_VERB "say_mod_verb" //Message modes. Each one defines a radio channel, more or less. diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index 25d4489e4a8..ba3678364db 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -23,7 +23,7 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list( list(/datum/quirk/social_anxiety, /datum/quirk/mute), list(/datum/quirk/mute, /datum/quirk/softspoken), list(/datum/quirk/poor_aim, /datum/quirk/bighands), - list(/datum/quirk/bilingual, /datum/quirk/foreigner), + list(/datum/quirk/bilingual, /datum/quirk/foreigner, /datum/quirk/csl), list(/datum/quirk/spacer_born, /datum/quirk/item_quirk/settler), list(/datum/quirk/photophobia, /datum/quirk/nyctophobia), list(/datum/quirk/item_quirk/settler, /datum/quirk/freerunning), diff --git a/code/datums/quirks/negative_quirks/csl.dm b/code/datums/quirks/negative_quirks/csl.dm new file mode 100644 index 00000000000..68a8f58be68 --- /dev/null +++ b/code/datums/quirks/negative_quirks/csl.dm @@ -0,0 +1,91 @@ +/datum/quirk/csl + name = "Common Second Language" + desc = "Common is not your native tongue - it's something you had to pick up along the way. \ + Some words in common will sound foreign, and you may drift back to your native tongue \ + when you are anxious or upset." + icon = FA_ICON_LANDMARK_DOME + quirk_flags = QUIRK_HIDE_FROM_SCAN + value = -2 + gain_text = span_danger("You have difficulty parsing Common.") + 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 + +/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.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)) + native_language = get_native_language() + +/datum/quirk/csl/remove() + UnregisterSignal(quirk_holder, COMSIG_SPECIES_GAIN) + UnregisterSignal(quirk_holder, COMSIG_MOB_SAY) + + if(QDELING(quirk_holder)) + return + + quirk_holder.remove_partial_language(/datum/language/common, type) + 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 + var/datum/language_holder/species_holder = GLOB.prototype_language_holders[carbon_quirk_holder.dna.species.species_language_holder] + if(LAZYACCESS(species_holder.spoken_languages, /datum/language/common)) + quirk_holder.grant_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + else + quirk_holder.grant_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_ATOM) + +/datum/quirk/csl/is_species_appropriate(datum/species/mob_species) + var/datum/language_holder/species_holder = GLOB.prototype_language_holders[mob_species.species_language_holder] + if(isnull(species_holder)) + return FALSE + if(length(species_holder.spoken_languages) < 2) + return FALSE + return ..() + +/// Gets our native language from our list of spoken languages +/datum/quirk/csl/proc/get_native_language() + var/list/language_pool = quirk_holder.get_language_holder()?.spoken_languages?.Copy() + if(!length(language_pool)) + return // no languages to pick from at all? + + // Don't want this + language_pool -= /datum/language/common + // If we have native languages set, prefer them + var/list/prioritized_language_pool + var/obj/item/organ/tongue/tongue = quirk_holder.get_organ_by_type(/obj/item/organ/tongue) + if(length(tongue?.languages_native) > 0) + prioritized_language_pool = language_pool & tongue.languages_native + + if(length(language_pool) < 1) + return // guess we couldn't find one + + return length(prioritized_language_pool) > 0 ? prioritized_language_pool[1] : language_pool[1] + +// Every time we change species we need to re-remove common from our list +/datum/quirk/csl/proc/reremove_common(...) + SIGNAL_HANDLER + quirk_holder.remove_language(/datum/language/common, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + native_language = get_native_language() + +// At low sanity we translate everything to our native language +/datum/quirk/csl/proc/translate_parts(datum/source, list/say_args) + SIGNAL_HANDLER + + 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][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 + associated_typepath = /datum/quirk/csl + customization_options = list( + /datum/preference/choiced/csl_strength, + ) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 6b7a22fb72f..3f79b1568a0 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1608,7 +1608,8 @@ return get_language_holder().get_random_understood_language() /// Gets a lazylist of all mutually understood languages. -/atom/movable/proc/get_partially_understood_languages() +/atom/movable/proc/get_partially_understood_languages() as /list + RETURN_TYPE(/list) return get_language_holder().best_mutual_languages /// Gets a random spoken language, useful for forced speech and such. diff --git a/code/game/machinery/telecomms/computers/logbrowser.dm b/code/game/machinery/telecomms/computers/logbrowser.dm index 5a9c3d6daac..e09e08b7a3d 100644 --- a/code/game/machinery/telecomms/computers/logbrowser.dm +++ b/code/game/machinery/telecomms/computers/logbrowser.dm @@ -59,7 +59,7 @@ message_out = "\"[message_in]\"" else if(!user.has_language(language)) // Language unknown: scramble - message_out = "\"[language_instance.scramble_sentence(message_in, user.get_partially_understood_languages())]\"" + message_out = "\"[language_instance.scramble_paragraph(message_in, user.get_partially_understood_languages())]\"" else message_out = "(Unintelligible)" packet_out["message"] = message_out diff --git a/code/game/say.dm b/code/game/say.dm index 8694d5b91a1..729504a5d0a 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -266,8 +266,16 @@ GLOBAL_LIST_INIT(freqtospan, list( return "makes a strange sound." if(!has_language(language)) + var/list/mutual_languages + // Get what we can kinda understand, factor in any bonuses passed in from say mods + var/list/partially_understood_languages = get_partially_understood_languages() + if(LAZYLEN(partially_understood_languages)) + mutual_languages = partially_understood_languages.Copy() + for(var/bonus_language in message_mods[LANGUAGE_MUTUAL_BONUS]) + mutual_languages[bonus_language] = max(message_mods[LANGUAGE_MUTUAL_BONUS][bonus_language], mutual_languages[bonus_language]) + var/datum/language/dialect = GLOB.language_datum_instances[language] - raw_message = dialect.scramble_sentence(raw_message, get_partially_understood_languages()) + raw_message = dialect.scramble_paragraph(raw_message, mutual_languages) return raw_message diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index acaedfb882b..ef6342c96d6 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -180,7 +180,7 @@ invocation = "Ta'gh fara'qha fel d'amar det!" /datum/action/innate/cult/blood_spell/emp/Activate() - owner.whisper(invocation, language = /datum/language/common) + owner.whisper(invocation, language = /datum/language/common, forced = "cult invocation") owner.visible_message(span_warning("[owner]'s hand flashes a bright blue!"), \ span_cult_italic("You speak the cursed words, emitting an EMP blast from your hand.")) empulse(owner, 2, 5) @@ -219,7 +219,7 @@ /datum/action/innate/cult/blood_spell/dagger/Activate() var/turf/owner_turf = get_turf(owner) - owner.whisper(invocation, language = /datum/language/common) + owner.whisper(invocation, language = /datum/language/common, forced = "cult invocation") owner.visible_message(span_warning("[owner]'s hand glows red for a moment."), \ span_cult_italic("Your plea for aid is answered, and light begins to shimmer and take form within your hand!")) var/obj/item/summoned_blade = new summoned_type(owner_turf) @@ -293,7 +293,7 @@ span_cult_italic("You invoke the veiling spell, hiding nearby runes.")) charges-- SEND_SOUND(owner, sound('sound/effects/magic/smoke.ogg',0,1,25)) - owner.whisper(invocation, language = /datum/language/common) + owner.whisper(invocation, language = /datum/language/common, forced = "cult invocation") for(var/obj/effect/rune/R in range(5,owner)) R.conceal() for(var/obj/structure/destructible/cult/S in range(5,owner)) @@ -311,7 +311,7 @@ owner.visible_message(span_warning("A flash of light shines from [owner]'s hand!"), \ span_cult_italic("You invoke the counterspell, revealing nearby runes.")) charges-- - owner.whisper(invocation, language = /datum/language/common) + owner.whisper(invocation, language = /datum/language/common, forced = "cult invocation") SEND_SOUND(owner, sound('sound/effects/magic/enter_blood.ogg',0,1,25)) for(var/obj/effect/rune/R in range(7,owner)) //More range in case you weren't standing in exactly the same spot R.reveal() @@ -413,7 +413,7 @@ /obj/item/melee/blood_magic/proc/cast_spell(atom/target, mob/living/carbon/user) if(invocation) - user.whisper(invocation, language = /datum/language/common) + user.whisper(invocation, language = /datum/language/common, forced = "cult invocation") if(health_cost) if(user.active_hand_index == 1) user.apply_damage(health_cost, BRUTE, BODY_ZONE_L_ARM, wound_bonus = CANT_WOUND) diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index 0724ce8be36..2dd0191d0ee 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -48,7 +48,7 @@ var/my_message if(!message || !user.mind) return - user.whisper("O bidai nabora se[pick("'","`")]sma!", language = /datum/language/common) + user.whisper("O bidai nabora se[pick("'","`")]sma!", language = /datum/language/common, forced = "cult invocation") user.whisper(html_decode(message), filterproof = TRUE) var/title = "Acolyte" var/span = "cult italic" diff --git a/code/modules/client/preferences/language.dm b/code/modules/client/preferences/language.dm index 7e961a65ff7..97d69ebf2f5 100644 --- a/code/modules/client/preferences/language.dm +++ b/code/modules/client/preferences/language.dm @@ -82,3 +82,21 @@ /datum/preference/choiced/language_skill/apply_to_human(mob/living/carbon/human/target, value) return + +/datum/preference/choiced/csl_strength + category = PREFERENCE_CATEGORY_MANUALLY_RENDERED + savefile_key = "csl_strength" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + +/datum/preference/choiced/csl_strength/create_default_value() + return "90%" + +/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%") + +/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 4dd2e58f98e..c6ca1d8717d 100644 --- a/code/modules/language/_language.dm +++ b/code/modules/language/_language.dm @@ -225,6 +225,22 @@ if(length(last_sentence_cache) > SENTENCE_CACHE_LEN) last_sentence_cache.Cut(1, last_sentence_cache.len - SENTENCE_CACHE_LEN + 1) +/** + * Scramble a paragraph in this language. + * + * Takes into account any languages the hearer knows that has mutual understanding with this language. + */ +/datum/language/proc/scramble_paragraph(input, list/mutual_languages) + // perfect understanding, no need to scramble + if(mutual_languages?[type] >= 100) + return input + + var/static/regex/first_sentence = regex(@"(.+?(?:[\.!\?]|$))", "g") + var/list/new_paragraph = list() + while(first_sentence.Find(input)) + new_paragraph += scramble_sentence(trim(first_sentence.group[1]), mutual_languages) + return jointext(new_paragraph, " ") + /** * Scrambles a sentence in this language. * @@ -232,39 +248,54 @@ */ /datum/language/proc/scramble_sentence(input, list/mutual_languages) var/cache_key = "[mutual_languages?[type] || 0]-understanding" - var/list/cache = read_sentence_cache(cache_key) + var/list/cache = read_sentence_cache(input) if(cache?[cache_key]) return cache[cache_key] - var/list/real_words = splittext(input, " ") + // List of words that will be recombined into a sentence var/list/scrambled_words = list() - for(var/word in real_words) + // List which indexes correspond to words in scrambled_words, records whether the word was translated + // Can't be a single assoc list because duplicates are expected + 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) if(translate_prob > 0) - // the probability of managing to understand a word is based on how common it is - // 1000 words in the list, so words outside the list are just treated as "the 1500th most common word" - var/commonness = GLOB.most_common_words[LOWER_TEXT(base_word)] || 1500 - translate_prob += (translate_prob * 0.2 * (1 - (min(commonness, 1500) / 500))) + // the probability of managing to understand a word is based on how common it is (+10%, -15%) + // 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[LOWER_TEXT(base_word)] || 1250 + translate_prob += (10 * (1 - (min(commonness, 1250) / 500))) if(prob(translate_prob)) - scrambled_words += base_word + scrambled_words += word + translated_index += FALSE continue - scrambled_words += scramble_word(base_word) + var/scrambled_word = scramble_word(base_word) + scrambled_words += scrambled_word + translated_index += (scrambled_word != base_word) // start building the new sentence. first word is capitalized and otherwise untouched - var/sentence = capitalize(popleft(scrambled_words)) - for(var/word in scrambled_words) - if(prob(between_word_sentence_chance)) + var/sentence = capitalize(scrambled_words[1]) + for(var/i in 2 to length(scrambled_words)) + var/word = scrambled_words[i] + // this was not translated so just throw it in + if(!translated_index[i]) + sentence += " [word]" + continue + // if the last word was scrambled, always include a space + if(translated_index[i - 1] || prob(between_word_space_chance)) + sentence += " " + // lastly try inserting a new sentence + else if(prob(between_word_sentence_chance)) sentence += ". " word = capitalize(word) - else if(prob(between_word_space_chance)) - sentence += " " sentence += word - // scrambling the words will drop punctuation, so re-add it at the end - sentence += find_last_punctuation(input) + // scrambling the word will drop punctuation, so we need to re-add it at the end + // (however we don't need to do anything if the last word was not translated) + if(translated_index[length(scrambled_words)]) + sentence += find_last_punctuation(input) write_sentence_cache(input, cache_key, sentence) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 74947d55f97..13e91c02b1f 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -417,16 +417,15 @@ taste_sensitivity = 32 liked_foodtypes = GROSS | MEAT | RAW | GORE disliked_foodtypes = NONE - -// List of english words that translate to zombie phrases -GLOBAL_LIST_INIT(english_to_zombie, list()) + // List of english words that translate to zombie phrases + var/static/list/english_to_zombie = list() /obj/item/organ/tongue/zombie/proc/add_word_to_translations(english_word, zombie_word) - GLOB.english_to_zombie[english_word] = zombie_word + english_to_zombie[english_word] = zombie_word // zombies don't care about grammar (any tense or form is all translated to the same word) - GLOB.english_to_zombie[english_word + plural_s(english_word)] = zombie_word - GLOB.english_to_zombie[english_word + "ing"] = zombie_word - GLOB.english_to_zombie[english_word + "ed"] = zombie_word + english_to_zombie[english_word + plural_s(english_word)] = zombie_word + english_to_zombie[english_word + "ing"] = zombie_word + english_to_zombie[english_word + "ed"] = zombie_word /obj/item/organ/tongue/zombie/proc/load_zombie_translations() var/list/zombie_translation = strings("zombie_replacement.json", "zombie") @@ -435,20 +434,20 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) var/list/data = islist(zombie_translation[zombie_word]) ? zombie_translation[zombie_word] : list(zombie_translation[zombie_word]) for(var/english_word in data) add_word_to_translations(english_word, zombie_word) - GLOB.english_to_zombie = sort_list(GLOB.english_to_zombie) // Alphabetizes the list (for debugging) + english_to_zombie = sort_list(english_to_zombie) // Alphabetizes the list (for debugging) /obj/item/organ/tongue/zombie/modify_speech(datum/source, list/speech_args) var/message = speech_args[SPEECH_MESSAGE] if(message[1] != "*") // setup the global list for translation if it hasn't already been done - if(!length(GLOB.english_to_zombie)) + if(!length(english_to_zombie)) load_zombie_translations() // make a list of all words that can be translated var/list/message_word_list = splittext(message, " ") var/list/translated_word_list = list() for(var/word in message_word_list) - word = GLOB.english_to_zombie[LOWER_TEXT(word)] + word = english_to_zombie[LOWER_TEXT(word)] translated_word_list += word ? word : FALSE // all occurrences of characters "eiou" (case-insensitive) are replaced with "r" @@ -483,6 +482,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED modifies_speech = TRUE // not really, they just hiss voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.8,aresample=%SAMPLE_RATE%,atempo=1/0.8,aformat=channel_layouts=mono [p0]; [out2] asetrate=%SAMPLE_RATE%*1.2,aresample=%SAMPLE_RATE%,atempo=1/1.2,aformat=channel_layouts=mono[p2]; [p0][0][p2] amix=inputs=3"} + // Aliens can only speak alien and a few other languages. /obj/item/organ/tongue/alien/get_possible_languages() return list( @@ -539,6 +539,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) modifies_speech = FALSE liked_foodtypes = VEGETABLES disliked_foodtypes = FRUIT | CLOTH + languages_native = list(/datum/language/calcic) /obj/item/organ/tongue/robot name = "robotic voicebox" @@ -589,6 +590,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) attack_verb_continuous = list("shocks", "jolts", "zaps") attack_verb_simple = list("shock", "jolt", "zap") voice_filter = @{"[0:a] asplit [out0][out2]; [out0] asetrate=%SAMPLE_RATE%*0.99,aresample=%SAMPLE_RATE%,volume=0.3 [p0]; [p0][out2] amix=inputs=2"} + languages_native = list(/datum/language/voltaic) // Ethereal tongues can speak all default + voltaic /obj/item/organ/tongue/ethereal/get_possible_languages() @@ -601,6 +603,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) liked_foodtypes = SEAFOOD | ORANGES | BUGS | GORE disliked_foodtypes = GROSS | CLOTH | RAW organ_traits = list(TRAIT_WOUND_LICKER, TRAIT_FISH_EATER) + languages_native = list(/datum/language/nekomimetic) /obj/item/organ/tongue/jelly name = "jelly tongue" @@ -609,6 +612,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) liked_foodtypes = MEAT | BUGS disliked_foodtypes = GROSS toxic_foodtypes = NONE + languages_native = list(/datum/language/slime) /obj/item/organ/tongue/jelly/get_food_taste_reaction(obj/item/food, foodtypes = NONE) // a silver slime created this? what a delicacy! @@ -622,6 +626,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) say_mod = "chimpers" liked_foodtypes = MEAT | FRUIT | BUGS disliked_foodtypes = CLOTH + languages_native = list(/datum/language/monkey) /obj/item/organ/tongue/moth name = "moth tongue" @@ -630,11 +635,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) liked_foodtypes = VEGETABLES | DAIRY | CLOTH disliked_foodtypes = FRUIT | GROSS | BUGS | GORE toxic_foodtypes = MEAT | RAW | SEAFOOD - -/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." - say_mod = "moans" + languages_native = list(/datum/language/moffic) /obj/item/organ/tongue/mush name = "mush-tongue-room" @@ -642,6 +643,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) icon = 'icons/obj/service/hydroponics/seeds.dmi' icon_state = "mycelium-angel" say_mod = "poofs" + languages_native = list(/datum/language/mushroom) /obj/item/organ/tongue/pod name = "pod tongue" @@ -651,6 +653,7 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) disliked_foodtypes = GORE | MEAT | DAIRY | SEAFOOD | BUGS foodtype_flags = PODPERSON_ORGAN_FOODTYPES color = COLOR_LIME + languages_native = list(/datum/language/sylvan) /obj/item/organ/tongue/golem name = "golem tongue" @@ -662,3 +665,4 @@ GLOBAL_LIST_INIT(english_to_zombie, list()) liked_foodtypes = STONE disliked_foodtypes = NONE //you don't care for much else besides stone toxic_foodtypes = NONE //you can eat fucking uranium + languages_native = list(/datum/language/terrum) diff --git a/tgstation.dme b/tgstation.dme index 6a01d4a6eec..2f68370c839 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1802,6 +1802,7 @@ #include "code\datums\quirks\negative_quirks\chronic_illness.dm" #include "code\datums\quirks\negative_quirks\claustrophobia.dm" #include "code\datums\quirks\negative_quirks\clumsy.dm" +#include "code\datums\quirks\negative_quirks\csl.dm" #include "code\datums\quirks\negative_quirks\cursed.dm" #include "code\datums\quirks\negative_quirks\deafness.dm" #include "code\datums\quirks\negative_quirks\depression.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/language.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/language.tsx index 836a600a724..8dd00ff9858 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/language.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/language.tsx @@ -21,3 +21,9 @@ export const language_skill: FeatureChoiced = { description: 'The percentage of the language you can understand.', component: FeatureDropdownInput, }; + +export const csl_strength: FeatureChoiced = { + name: 'Language Skill', + description: 'The percentage of Common you can understand.', + component: FeatureDropdownInput, +};