diff --git a/Content.Server/_DV/Speech/Components/SlavicAccentComponent.cs b/Content.Server/_DV/Speech/Components/SlavicAccentComponent.cs new file mode 100644 index 0000000000..57bb6d1b55 --- /dev/null +++ b/Content.Server/_DV/Speech/Components/SlavicAccentComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.Speech.Components; + +[RegisterComponent] +public sealed partial class SlavicAccentComponent : Component +{ + /// + /// The chance (0.0 to 1.0) that articles like "the", "a", "an" will be removed from sentences, default is 80%. + /// + [DataField] + public float ArticleRemovalChance = 0.8f; + + /// + /// The chance (0.0 to 1.0) that "tovarisch" will be replaced with "komrade" (comrade) instead, default is 20%. + /// + [DataField] + public float KomradeReplacementChance = 0.2f; +} diff --git a/Content.Server/_DV/Speech/EntitySystems/SlavicAccentSystem.cs b/Content.Server/_DV/Speech/EntitySystems/SlavicAccentSystem.cs new file mode 100644 index 0000000000..b01d8394f0 --- /dev/null +++ b/Content.Server/_DV/Speech/EntitySystems/SlavicAccentSystem.cs @@ -0,0 +1,188 @@ +using System.Text.RegularExpressions; +using Content.Server.Speech.Components; +using Content.Shared.Speech; +using Robust.Shared.Random; + +namespace Content.Server.Speech.EntitySystems; + +public sealed class SlavicAccentSystem : EntitySystem +{ + [Dependency] private readonly ReplacementAccentSystem _replacement = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + // Sound replacement regexes + private static readonly Regex ThToZVowelRegex = new(@"\bTh(?=[aeiou])", RegexOptions.Compiled); + private static readonly Regex ThToZWordsRegex = new(@"Th(?=at|is|ese|ose|ey|em|an)", RegexOptions.Compiled); + private static readonly Regex AllCapsThToZVowelRegex = new(@"\bTH(?=[AEIOU])", RegexOptions.Compiled); + private static readonly Regex AllCapsThToZWordsRegex = new(@"TH(?=AT|IS|ESE|OSE|EY|EM|AN)", RegexOptions.Compiled); + private static readonly Regex LowercaseThToZVowelRegex = new(@"\bth(?=[aeiou])", RegexOptions.Compiled); + private static readonly Regex LowercaseThToZWordsRegex = new(@"th(?=at|is|ese|ose|ey|em|an)", RegexOptions.Compiled); + private static readonly Regex CToKCapitalRegex = new(@"\bC", RegexOptions.Compiled); + private static readonly Regex CToKLowercaseRegex = new(@"\bc", RegexOptions.Compiled); + private static readonly Regex WToVCapitalRegex = new(@"\bW", RegexOptions.Compiled); + private static readonly Regex WToVLowercaseRegex = new(@"\bw", RegexOptions.Compiled); + private static readonly Regex DentalTInVowelsRegex = new(@"(?<=[aeiouAEIOU])t(?=[aeiouAEIOU])", RegexOptions.Compiled); + private static readonly Regex EeRegex = new(@"ee", RegexOptions.Compiled); + + // Grammar replacement regexes + private static readonly Regex TheLowercaseRegex = new(@"\bthe\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex ALowercaseRegex = new(@"\ba\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex AnLowercaseRegex = new(@"\ban\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex IsLowercaseRegex = new(@"\bis\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex AreLowercaseRegex = new(@"\bare\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex IAmLowercaseRegex = new(@"\bI am\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex WhitespaceRegex = new(@" +", RegexOptions.Compiled); + + private static readonly Regex TovarischRegex = new(@"\btovarisch\b", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + public override void Initialize() + { + SubscribeLocalEvent(OnAccent); + } + + // Applies Slavic accent to a message + public string Accentuate(string message, SlavicAccentComponent component) + { + var accentedMessage = _replacement.ApplyReplacements(message, "slavic"); + accentedMessage = ApplyKomradeReplacement(accentedMessage, component); + accentedMessage = ApplyGrammarRules(accentedMessage, component); + accentedMessage = ApplySoundReplacements(accentedMessage); + return accentedMessage; + } + + // Randomly replaces 'tovarisch' with 'Komrade' while preserving capitalization. + // TODO: The ReplacementAccentSystem REALLY should have random replacements built-in. + private string ApplyKomradeReplacement(string message, SlavicAccentComponent component) + { + return TovarischRegex.Replace(message, match => + { + if (!_random.Prob(component.KomradeReplacementChance)) + return match.Value; + var original = match.Value; + if (IsAllUpperCase(original)) + return "KOMRADE"; + if (IsCapitalized(original)) + return "Komrade"; + return "komrade"; + }); + } + + private static bool IsAllUpperCase(string text) + { + if (string.IsNullOrEmpty(text)) return false; + foreach (var c in text) + if (char.IsLetter(c) && !char.IsUpper(c)) return false; + return true; + } + + private static bool IsCapitalized(string text) + { + if (string.IsNullOrEmpty(text) || !char.IsLetter(text[0])) return false; + if (!char.IsUpper(text[0])) return false; + for (int i = 1; i < text.Length; i++) + if (char.IsLetter(text[i]) && !char.IsLower(text[i])) return false; + return true; + } + + /// + /// Applies sound-level replacements to simulate Slavic accent phonetics. + /// + private static string ApplySoundReplacements(string message) + { + if (string.IsNullOrEmpty(message)) + return message; + + var result = message; + + // Apply TH replacements (grouped by case) + result = ThToZVowelRegex.Replace(result, "Z"); + result = ThToZWordsRegex.Replace(result, "Z"); + result = AllCapsThToZVowelRegex.Replace(result, "Z"); + result = AllCapsThToZWordsRegex.Replace(result, "Z"); + result = LowercaseThToZVowelRegex.Replace(result, "z"); + result = LowercaseThToZWordsRegex.Replace(result, "z"); + + // Apply other consonant replacements + result = CToKCapitalRegex.Replace(result, "K"); + result = CToKLowercaseRegex.Replace(result, "k"); + result = WToVCapitalRegex.Replace(result, "V"); + result = WToVLowercaseRegex.Replace(result, "v"); + + // Apply vowel and other sound changes + result = DentalTInVowelsRegex.Replace(result, "th"); + result = EeRegex.Replace(result, "i"); + + // Restore capitalization + if (result.Length > 0 && message.Length > 0 && + char.IsLetter(message[0]) && char.IsLower(result[0]) && char.IsUpper(message[0])) + { + result = char.ToUpper(result[0]) + result[1..]; + } + + return result; + } + + // Applies grammar rules typical of Slavic-accented English, such as article and verb removal. + private string ApplyGrammarRules(string message, SlavicAccentComponent component) + { + if (string.IsNullOrEmpty(message)) + return message; + + var wasFirstLetterCapitalized = char.IsUpper(message[0]); + + // Early exit if its not long enough + var wordCount = message.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; + if (wordCount <= 3) + { + return message; + } + + // Instead of checking per word we're just gonna check for the whole message. + if (!_random.Prob(component.ArticleRemovalChance)) + { + return message; + } + + var result = message; + + // If a message starts with any of these we remove em if ArticalRemovalChance passes. This is more optimized than doing a regex check. + if (result.StartsWith("The ", StringComparison.Ordinal)) + result = result.Substring(4); + else if (result.StartsWith("THE ", StringComparison.Ordinal)) + result = result.Substring(4); + else if (result.StartsWith("A ", StringComparison.Ordinal)) + result = result.Substring(2); + else if (result.StartsWith("An ", StringComparison.Ordinal)) + result = result.Substring(3); + else + { + // Apply regex replacements for articles elsewhere in the message + result = TheLowercaseRegex.Replace(result, ""); + result = ALowercaseRegex.Replace(result, ""); + result = AnLowercaseRegex.Replace(result, ""); + } + + // Remove verbs + result = IsLowercaseRegex.Replace(result, ""); + result = AreLowercaseRegex.Replace(result, ""); + + // Simplify "I am" to "I" + result = IAmLowercaseRegex.Replace(result, "I"); + + // Clean up whitespace + result = WhitespaceRegex.Replace(result.Trim(), " "); + + // Restore capitalization + if (wasFirstLetterCapitalized && !string.IsNullOrEmpty(result) && char.IsLetter(result[0]) && char.IsLower(result[0])) + { + result = char.ToUpper(result[0]) + result.Substring(1); + } + + return result; + } + + private void OnAccent(Entity entity, ref AccentGetEvent args) + { + args.Message = Accentuate(args.Message, entity.Comp); + } +} diff --git a/Resources/Locale/en-US/_DV/accent/slavic.ftl b/Resources/Locale/en-US/_DV/accent/slavic.ftl new file mode 100644 index 0000000000..141f2556b0 --- /dev/null +++ b/Resources/Locale/en-US/_DV/accent/slavic.ftl @@ -0,0 +1,101 @@ +accent-slavic-words-1 = yes +accent-slavic-words-replace-1 = da + +accent-slavic-words-2 = no +accent-slavic-words-replace-2 = nyet + +accent-slavic-words-3 = grandma +accent-slavic-words-3-2 = grandmother +accent-slavic-words-3-3 = granny +accent-slavic-words-replace-3 = babushka + +accent-slavic-words-4 = grandfather +accent-slavic-words-4-2 = grandpa +accent-slavic-words-4-3 = gramps +accent-slavic-words-replace-4 = dedushka + +accent-slavic-words-5 = friend +accent-slavic-words-5-2 = buddy +accent-slavic-words-replace-5 = tovarisch + +accent-slavic-words-6 = idiot +accent-slavic-words-6-2 = fool +accent-slavic-words-replace-6 = durak + +accent-slavic-words-7 = idiots +accent-slavic-words-7-2 = fools +accent-slavic-words-replace-7 = duraki + +accent-slavic-words-8 = friends +accent-slavic-words-replace-8 = tovarisch + +accent-slavic-words-9 = cheers +accent-slavic-words-replace-9 = na zdorovje + +accent-slavic-words-10 = damn +accent-slavic-words-10-2 = dammit +accent-slavic-words-replace-10 = blin + +accent-slavic-words-11 = shit +accent-slavic-words-replace-11 = blyat + +accent-slavic-words-12 = hello +accent-slavic-words-replace-12 = privet + +accent-slavic-words-13 = goodbye +accent-slavic-words-replace-13 = do svidaniya + +accent-slavic-words-14 = thank you +accent-slavic-words-14-2 = thanks +accent-slavic-words-replace-14 = spasibo + +accent-slavic-words-15 = good +accent-slavic-words-replace-15 = dobro + +accent-slavic-words-16 = fuck +accent-slavic-words-16-2 = bitch +accent-slavic-words-replace-16 = kurwa + +accent-slavic-words-17 = beautiful +accent-slavic-words-replace-17 = krasivaya + +accent-slavic-words-18 = goddammit +accent-slavic-words-18-2 = fucking hell +accent-slavic-words-18-3 = shit fuck +accent-slavic-words-18-4 = what the fuck +accent-slavic-words-18-5 = motherfucker +accent-slavic-words-18-6 = son of a bitch +accent-slavic-words-replace-18 = cyka blyat + +accent-slavic-words-19 = oh my god +accent-slavic-words-19-2 = oh my +accent-slavic-words-19-3 = my god +accent-slavic-words-replace-19 = bozhe moy + +accent-slavic-words-20 = oh no +accent-slavic-words-20-2 = uh oh +accent-slavic-words-replace-20 = oy blin + +accent-slavic-words-21 = coffee +accent-slavic-words-replace-21 = kofye + +accent-slavic-words-22 = tea +accent-slavic-words-replace-22 = chai + +accent-slavic-words-23 = mom +accent-slavic-words-23-2 = mother +accent-slavic-words-replace-23 = mama + +accent-slavic-words-24 = dad +accent-slavic-words-24-2 = father +accent-slavic-words-replace-24 = papa + +accent-slavic-words-25 = bye +accent-slavic-words-replace-25 = ciao + +accent-slavic-words-26 = small +accent-slavic-words-replace-26 = maly + +accent-slavic-words-27 = big +accent-slavic-words-27-2 = large +accent-slavic-words-replace-27 = veliky diff --git a/Resources/Locale/en-US/_DV/traits/traits.ftl b/Resources/Locale/en-US/_DV/traits/traits.ftl index c568c3fb43..e3702af46f 100644 --- a/Resources/Locale/en-US/_DV/traits/traits.ftl +++ b/Resources/Locale/en-US/_DV/traits/traits.ftl @@ -67,3 +67,6 @@ trait-anglish-desc = Your tung is of the Saxons, and say unlike others aboard th trait-protected-name = Marked as Protected trait-protected-desc = Due to your position, value, or circumstances, your survival is considered preferable. You won't be targeted by kill objectives. + +trait-slavic-name = Slavic accent +trait-slavic-desc = You kome from ze East, and speak vith strong Slavic accent! diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index 61fbe53399..7a3f9d99cf 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml @@ -74,6 +74,7 @@ - UltraVision - Unborgable - AnglishAccent + - SlavicAccent # End DeltaV Additions blacklist: components: diff --git a/Resources/Prototypes/_DV/Accents/word_replacements.yml b/Resources/Prototypes/_DV/Accents/word_replacements.yml index 9b600b1e61..6338843bbd 100644 --- a/Resources/Prototypes/_DV/Accents/word_replacements.yml +++ b/Resources/Prototypes/_DV/Accents/word_replacements.yml @@ -1564,3 +1564,56 @@ accent-anglish-words-1165: accent-anglish-replacement-1165 accent-anglish-words-1166: accent-anglish-replacement-1166 accent-anglish-words-1167: accent-anglish-replacement-1167 + +#Slavic Accent +- type: accent + id: slavic + wordReplacements: + accent-slavic-words-1: accent-slavic-words-replace-1 + accent-slavic-words-2: accent-slavic-words-replace-2 + accent-slavic-words-3: accent-slavic-words-replace-3 + accent-slavic-words-3-2: accent-slavic-words-replace-3 + accent-slavic-words-3-3: accent-slavic-words-replace-3 + accent-slavic-words-4: accent-slavic-words-replace-4 + accent-slavic-words-4-2: accent-slavic-words-replace-4 + accent-slavic-words-4-3: accent-slavic-words-replace-4 + accent-slavic-words-5: accent-slavic-words-replace-5 + accent-slavic-words-5-2: accent-slavic-words-replace-5 + accent-slavic-words-6: accent-slavic-words-replace-6 + accent-slavic-words-6-2: accent-slavic-words-replace-6 + accent-slavic-words-7: accent-slavic-words-replace-7 + accent-slavic-words-7-2: accent-slavic-words-replace-7 + accent-slavic-words-8: accent-slavic-words-replace-8 + accent-slavic-words-9: accent-slavic-words-replace-9 + accent-slavic-words-10: accent-slavic-words-replace-10 + accent-slavic-words-10-2: accent-slavic-words-replace-10 + accent-slavic-words-11: accent-slavic-words-replace-11 + accent-slavic-words-12: accent-slavic-words-replace-12 + accent-slavic-words-13: accent-slavic-words-replace-13 + accent-slavic-words-14: accent-slavic-words-replace-14 + accent-slavic-words-14-2: accent-slavic-words-replace-14 + accent-slavic-words-15: accent-slavic-words-replace-15 + accent-slavic-words-16: accent-slavic-words-replace-16 + accent-slavic-words-16-2: accent-slavic-words-replace-16 + accent-slavic-words-17: accent-slavic-words-replace-17 + accent-slavic-words-18: accent-slavic-words-replace-18 + accent-slavic-words-18-2: accent-slavic-words-replace-18 + accent-slavic-words-18-3: accent-slavic-words-replace-18 + accent-slavic-words-18-4: accent-slavic-words-replace-18 + accent-slavic-words-18-5: accent-slavic-words-replace-18 + accent-slavic-words-18-6: accent-slavic-words-replace-18 + accent-slavic-words-19: accent-slavic-words-replace-19 + accent-slavic-words-19-2: accent-slavic-words-replace-19 + accent-slavic-words-19-3: accent-slavic-words-replace-19 + accent-slavic-words-20: accent-slavic-words-replace-20 + accent-slavic-words-20-2: accent-slavic-words-replace-20 + accent-slavic-words-21: accent-slavic-words-replace-21 + accent-slavic-words-22: accent-slavic-words-replace-22 + accent-slavic-words-23: accent-slavic-words-replace-23 + accent-slavic-words-23-2: accent-slavic-words-replace-23 + accent-slavic-words-24: accent-slavic-words-replace-24 + accent-slavic-words-24-2: accent-slavic-words-replace-24 + accent-slavic-words-25: accent-slavic-words-replace-25 + accent-slavic-words-26: accent-slavic-words-replace-26 + accent-slavic-words-27: accent-slavic-words-replace-27 + accent-slavic-words-27-2: accent-slavic-words-replace-27 diff --git a/Resources/Prototypes/_DV/Traits/speech.yml b/Resources/Prototypes/_DV/Traits/speech.yml index 389ce3345f..a02e287017 100644 --- a/Resources/Prototypes/_DV/Traits/speech.yml +++ b/Resources/Prototypes/_DV/Traits/speech.yml @@ -185,3 +185,14 @@ - !type:AddCompsEffect components: - type: AnglishAccent + +- type: trait + id: SlavicAccent + name: trait-slavic-name + description: trait-slavic-desc + category: Accents + cost: 2 + effects: + - !type:AddCompsEffect + components: + - type: SlavicAccent