Delta-v/Content.Server/_DV/Speech/EntitySystems/SlavicAccentSystem.cs

189 lines
8.1 KiB
C#

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);
}
}