Slavic Accent character trait (#5264)

* port slavic accent

* change some replacements

* bobr

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* eliminate redundant regex

* bozhe moy

* order matters

* Merge branch 'master' into slavicAccent

Signed-off-by: zelezniciar1 <39102800+zelezniciar1@users.noreply.github.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

Co-authored-by: Tobias Berger <toby@tobot.dev>
Signed-off-by: zelezniciar1 <39102800+zelezniciar1@users.noreply.github.com>

* Update Content.Server/_DV/Speech/EntitySystems/SlavicAccentSystem.cs

Co-authored-by: BarryNorfolk <barrynorfolkman@protonmail.com>
Signed-off-by: zelezniciar1 <39102800+zelezniciar1@users.noreply.github.com>

* tryfix

* Merge branch 'slavicAccent' of https://github.com/zelezniciar1/Delta-v into slavicAccent
This commit is contained in:
zelezniciar1 2026-02-27 18:30:55 +00:00 committed by GitHub
parent c4d8441791
commit 22b3945a86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 374 additions and 0 deletions

View File

@ -0,0 +1,17 @@
namespace Content.Server.Speech.Components;
[RegisterComponent]
public sealed partial class SlavicAccentComponent : Component
{
/// <summary>
/// The chance (0.0 to 1.0) that articles like "the", "a", "an" will be removed from sentences, default is 80%.
/// </summary>
[DataField]
public float ArticleRemovalChance = 0.8f;
/// <summary>
/// The chance (0.0 to 1.0) that "tovarisch" will be replaced with "komrade" (comrade) instead, default is 20%.
/// </summary>
[DataField]
public float KomradeReplacementChance = 0.2f;
}

View File

@ -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<SlavicAccentComponent, AccentGetEvent>(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;
}
/// <summary>
/// Applies sound-level replacements to simulate Slavic accent phonetics.
/// </summary>
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<SlavicAccentComponent> entity, ref AccentGetEvent args)
{
args.Message = Accentuate(args.Message, entity.Comp);
}
}

View File

@ -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

View File

@ -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!

View File

@ -74,6 +74,7 @@
- UltraVision
- Unborgable
- AnglishAccent
- SlavicAccent
# End DeltaV Additions
blacklist:
components:

View File

@ -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

View File

@ -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