From 18df657fb647887c817de85ee960249e5be2bb04 Mon Sep 17 00:00:00 2001 From: Kara Date: Mon, 3 Apr 2023 19:50:37 -0700 Subject: [PATCH] Word replacement accent system (#15086) --- .../Speech/Components/DwarfAccentComponent.cs | 9 -- .../Components/ReplacementAccentComponent.cs | 16 ++- .../Speech/EntitySystems/DwarfAccentSystem.cs | 83 ------------- .../EntitySystems/MobsterAccentSystem.cs | 9 +- .../EntitySystems/ReplacementAccentSystem.cs | 65 ++++++++++- Resources/Locale/en-US/accent/dwarf.ftl | 109 ++++++++++++++++++ Resources/Locale/en-US/accent/mobster.ftl | 51 ++++++++ .../full_replacements.yml} | 60 +++++----- .../Prototypes/Accents/word_replacements.yml | 63 ++++++++++ .../Entities/Mobs/Species/dwarf.yml | 3 +- Resources/keybinds.yml | 20 +--- 11 files changed, 338 insertions(+), 150 deletions(-) delete mode 100644 Content.Server/Speech/Components/DwarfAccentComponent.cs delete mode 100644 Content.Server/Speech/EntitySystems/DwarfAccentSystem.cs create mode 100644 Resources/Locale/en-US/accent/dwarf.ftl rename Resources/Prototypes/{accents.yml => Accents/full_replacements.yml} (58%) create mode 100644 Resources/Prototypes/Accents/word_replacements.yml diff --git a/Content.Server/Speech/Components/DwarfAccentComponent.cs b/Content.Server/Speech/Components/DwarfAccentComponent.cs deleted file mode 100644 index 87609384f9..0000000000 --- a/Content.Server/Speech/Components/DwarfAccentComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Speech.Components; - -/// -/// Used for replacing words for dwarves (pseudo-scottish) -/// -[RegisterComponent] -public sealed class DwarfAccentComponent : Component -{ -} diff --git a/Content.Server/Speech/Components/ReplacementAccentComponent.cs b/Content.Server/Speech/Components/ReplacementAccentComponent.cs index d791675de4..ee3a102be3 100644 --- a/Content.Server/Speech/Components/ReplacementAccentComponent.cs +++ b/Content.Server/Speech/Components/ReplacementAccentComponent.cs @@ -10,12 +10,22 @@ namespace Content.Server.Speech.Components [IdDataField] public string ID { get; } = default!; - [DataField("words")] - public string[] Words = default!; + /// + /// If this array is non-null, the full text of anything said will be randomly replaced with one of these words. + /// + [DataField("fullReplacements")] + public string[]? FullReplacements; + + /// + /// If this dictionary is non-null and is null, any keys surrounded by spaces + /// (words) will be replaced by the value, attempting to intelligently keep capitalization. + /// + [DataField("wordReplacements")] + public Dictionary? WordReplacements; } /// - /// Replaces any spoken sentences with a random word. + /// Replaces full sentences or words within sentences with new strings. /// [RegisterComponent] public sealed class ReplacementAccentComponent : Component diff --git a/Content.Server/Speech/EntitySystems/DwarfAccentSystem.cs b/Content.Server/Speech/EntitySystems/DwarfAccentSystem.cs deleted file mode 100644 index 4231f26968..0000000000 --- a/Content.Server/Speech/EntitySystems/DwarfAccentSystem.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Text.RegularExpressions; -using Content.Server.Speech.Components; - -namespace Content.Server.Speech.EntitySystems; - -public sealed class DwarfAccentSystem : EntitySystem -{ - // TODO: - // these are pretty bad to have as static dicts in systems, ideally these all get moved to prototypes - // these can honestly stay unlocalized in prototypes? -- most of these word-replacers make zero sense to localize into other languages - // since they're so english-specific - // all of the 'word-replacers' should also probably respect capitalization when transformed, so all caps -> all caps - // and first letter capitalized -> first letter capitalized, at the very least - - // these specifically mostly come from examples of specific scottish-english (not necessarily scots) verbiage - // https://en.wikipedia.org/wiki/Scotticism - // https://en.wikipedia.org/wiki/Scottish_English - // https://www.cs.stir.ac.uk/~kjt/general/scots.html - private static readonly Dictionary DirectReplacements = new() - { - { "girl", "lassie" }, - { "boy", "laddie" }, - { "man", "lad" }, - { "woman", "lass" }, - { "do", "dae" }, - { "don't", "dinnae" }, - { "dont", "dinnae" }, - { "i'm", "A'm" }, - { "im", "am"}, - { "going", "gaun" }, - { "know", "ken"}, - { "i", "Ah" }, - { "you're", "ye're"}, - { "youre", "yere"}, - { "you", "ye" }, - { "i'll", "A'll" }, - { "ill", "all"}, - { "of", "ae" }, - { "was", "wis" }, - { "can't", "cannae" }, - { "cant", "cannae" }, - { "yourself", "yersel" }, - { "where", "whaur" }, - { "oh", "ach" }, - { "little", "wee" }, - { "small", "wee" }, - { "shit", "shite" }, - { "yeah", "aye" }, - { "yea", "aye"}, - { "yes", "aye" }, - { "too", "tae" }, - { "my", "ma" }, - { "not", "nae" }, - { "dad", "da" }, - { "mom", "maw" }, - }; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAccentGet); - } - - public string Accentuate(string message) - { - // this is just word replacements right now, - // but leaving it open to more intelligent phonotactic manipulations at some point which are probably possible - var msg = message; - - foreach (var (first, replace) in DirectReplacements) - { - msg = Regex.Replace(msg, $@"(? DirectReplacements = new() { @@ -43,12 +44,8 @@ public sealed class MobsterAccentSystem : EntitySystem // Do text manipulations first // Then prefix/suffix funnyies - var msg = message; - - foreach (var (first, replace) in DirectReplacements) - { - msg = Regex.Replace(msg, $@"(? thinkin' // king -> king diff --git a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs index 9834fba21b..0671a25201 100644 --- a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs @@ -1,4 +1,7 @@ +using System.Linq; +using System.Text.RegularExpressions; using Content.Server.Speech.Components; +using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -6,12 +9,13 @@ namespace Content.Server.Speech.EntitySystems { // TODO: Code in-game languages and make this a language /// - /// Replaces any spoken sentences with a random word. + /// Replaces text in messages, either with full replacements or word replacements. /// public sealed class ReplacementAccentSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; public override void Initialize() { @@ -20,9 +24,64 @@ namespace Content.Server.Speech.EntitySystems private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args) { - var words = _proto.Index(component.Accent).Words; + args.Message = ApplyReplacements(args.Message, component.Accent); + } + + /// + /// Attempts to apply a given replacement accent prototype to a message. + /// + [PublicAPI] + public string ApplyReplacements(string message, string accent) + { + if (!_proto.TryIndex(accent, out var prototype)) + return message; + + // Prioritize fully replacing if that exists-- + // ideally both aren't used at the same time (but we don't have a way to enforce that in serialization yet) + if (prototype.FullReplacements != null) + { + return prototype.FullReplacements.Length != 0 ? Loc.GetString(_random.Pick(prototype.FullReplacements)) : ""; + } + + if (prototype.WordReplacements == null) + return message; + + foreach (var (first, replace) in prototype.WordReplacements) + { + var f = _loc.GetString(first); + var r = _loc.GetString(replace); + // this is kind of slow but its not that bad + // essentially: go over all matches, try to match capitalization where possible, then replace + // rather than using regex.replace + foreach (Match match in Regex.Matches(message, $@"(? Ah, without that it would transform I -> AH + // so that second case will only fully-uppercase if the replacement length is also 1 + if (!match.Value.Any(char.IsLower) && (match.Length > 1 || replacement.Length == 1)) + { + replacement = replacement.ToUpperInvariant(); + } + else if (match.Length >= 1 && replacement.Length >= 1 && char.IsUpper(match.Value[0])) + { + replacement = replacement[0].ToString().ToUpper() + replacement[1..]; + } + + // In-place replace the match with the transformed capitalization replacement + message = message.Remove(match.Index, match.Length).Insert(match.Index, replacement); + } + } - args.Message = words.Length != 0 ? Loc.GetString(_random.Pick(words)) : ""; + return message; } } } diff --git a/Resources/Locale/en-US/accent/dwarf.ftl b/Resources/Locale/en-US/accent/dwarf.ftl new file mode 100644 index 0000000000..44673062af --- /dev/null +++ b/Resources/Locale/en-US/accent/dwarf.ftl @@ -0,0 +1,109 @@ +# these specifically mostly come from examples of specific scottish-english (not necessarily scots) verbiage +# https://en.wikipedia.org/wiki/Scotticism +# https://en.wikipedia.org/wiki/Scottish_English +# https://www.cs.stir.ac.uk/~kjt/general/scots.html + +accent-dwarf-words-1 = girl +accent-dwarf-words-replace-1 = lassie + +accent-dwarf-words-2 = boy +accent-dwarf-words-replace-2 = laddie + +accent-dwarf-words-3 = man +accent-dwarf-words-replace-3 = lad + +accent-dwarf-words-4 = woman +accent-dwarf-words-replace-4 = lass + +accent-dwarf-words-5 = do +accent-dwarf-words-replace-5 = dae + +accent-dwarf-words-6 = don't +accent-dwarf-words-replace-6 = dinnae + +accent-dwarf-words-7 = dont +accent-dwarf-words-replace-7 = dinnae + +accent-dwarf-words-8 = i'm +accent-dwarf-words-replace-8 = A'm + +accent-dwarf-words-9 = im +accent-dwarf-words-replace-9 = am + +accent-dwarf-words-10 = going +accent-dwarf-words-replace-10 = gaun + +accent-dwarf-words-11 = know +accent-dwarf-words-replace-11 = ken + +accent-dwarf-words-12 = i +accent-dwarf-words-replace-12 = Ah + +accent-dwarf-words-13 = you're +accent-dwarf-words-replace-13 = ye're + +accent-dwarf-words-14 = youre +accent-dwarf-words-replace-14 = yere + +accent-dwarf-words-15 = you +accent-dwarf-words-replace-15 = ye + +accent-dwarf-words-16 = i'll +accent-dwarf-words-replace-16 = A'll + +accent-dwarf-words-17 = ill +accent-dwarf-words-replace-17 = all + +accent-dwarf-words-18 = of +accent-dwarf-words-replace-18 = ae + +accent-dwarf-words-19 = was +accent-dwarf-words-replace-19 = wis + +accent-dwarf-words-20 = can't +accent-dwarf-words-replace-20 = cannae + +accent-dwarf-words-21 = cant +accent-dwarf-words-replace-21 = cannae + +accent-dwarf-words-22 = yourself +accent-dwarf-words-replace-22 = yersel + +accent-dwarf-words-23 = where +accent-dwarf-words-replace-23 = whaur + +accent-dwarf-words-24 = oh +accent-dwarf-words-replace-24 = ach + +accent-dwarf-words-25 = little +accent-dwarf-words-replace-25 = wee + +accent-dwarf-words-26 = small +accent-dwarf-words-replace-26 = wee + +accent-dwarf-words-27 = shit +accent-dwarf-words-replace-27 = shite + +accent-dwarf-words-28 = yeah +accent-dwarf-words-replace-28 = aye + +accent-dwarf-words-29 = yea +accent-dwarf-words-replace-29 = aye + +accent-dwarf-words-30 = yes +accent-dwarf-words-replace-30 = aye + +accent-dwarf-words-31 = too +accent-dwarf-words-replace-31 = tae + +accent-dwarf-words-32 = my +accent-dwarf-words-replace-32 = ma + +accent-dwarf-words-33 = not +accent-dwarf-words-replace-33 = nae + +accent-dwarf-words-34 = dad +accent-dwarf-words-replace-34 = da + +accent-dwarf-words-35 = mom +accent-dwarf-words-replace-35 = maw diff --git a/Resources/Locale/en-US/accent/mobster.ftl b/Resources/Locale/en-US/accent/mobster.ftl index 9aca97971b..d0f510453d 100644 --- a/Resources/Locale/en-US/accent/mobster.ftl +++ b/Resources/Locale/en-US/accent/mobster.ftl @@ -6,3 +6,54 @@ accent-mobster-suffix-boss-3 = , capiche? accent-mobster-suffix-minion-1 = , yeah! accent-mobster-suffix-minion-2 = , boss says! + +accent-mobster-words-1 = let me +accent-mobster-words-replace-1 = lemme + +accent-mobster-words-2 = should +accent-mobster-words-replace-2 = oughta + +accent-mobster-words-3 = the +accent-mobster-words-replace-3 = da + +accent-mobster-words-4 = them +accent-mobster-words-replace-4 = dem + +accent-mobster-words-5 = attack +accent-mobster-words-replace-5 = whack + +accent-mobster-words-6 = kill +accent-mobster-words-replace-6 = whack + +accent-mobster-words-7 = murder +accent-mobster-words-replace-7 = whack + +accent-mobster-words-8 = dead +accent-mobster-words-replace-8 = sleepin' with da fishies + +accent-mobster-words-9 = hey +accent-mobster-words-replace-9 = ey'o + +accent-mobster-words-10 = hi +accent-mobster-words-replace-10 = ey'o + +accent-mobster-words-11 = hello +accent-mobster-words-replace-11 = ey'o + +accent-mobster-words-12 = rules +accent-mobster-words-replace-12 = roolz + +accent-mobster-words-13 = you +accent-mobster-words-replace-13 = yous + +accent-mobster-words-14 = have to +accent-mobster-words-replace-14 = gotta + +accent-mobster-words-15 = going to +accent-mobster-words-replace-15 = boutta + +accent-mobster-words-16 = about to +accent-mobster-words-replace-16 = boutta + +accent-mobster-words-17 = here +accent-mobster-words-replace-17 = 'ere diff --git a/Resources/Prototypes/accents.yml b/Resources/Prototypes/Accents/full_replacements.yml similarity index 58% rename from Resources/Prototypes/accents.yml rename to Resources/Prototypes/Accents/full_replacements.yml index b731559c89..45ce49d653 100644 --- a/Resources/Prototypes/accents.yml +++ b/Resources/Prototypes/Accents/full_replacements.yml @@ -1,6 +1,8 @@ +# Accents that work off of full replacements rather than word replacements. + - type: accent id: cat - words: + fullReplacements: - accent-words-cat-1 - accent-words-cat-2 - accent-words-cat-3 @@ -9,7 +11,7 @@ - type: accent id: dog - words: + fullReplacements: - accent-words-dog-1 - accent-words-dog-2 - accent-words-dog-3 @@ -18,7 +20,7 @@ - type: accent id: mouse - words: + fullReplacements: - accent-words-mouse-1 - accent-words-mouse-2 - accent-words-mouse-3 @@ -26,41 +28,41 @@ - type: accent id: mumble - words: - - accent-words-mumble-1 - - accent-words-mumble-2 - - accent-words-mumble-3 + fullReplacements: + - accent-words-mumble-1 + - accent-words-mumble-2 + - accent-words-mumble-3 - type: accent id: silicon - words: - - accent-words-silicon-1 - - accent-words-silicon-2 - - accent-words-silicon-3 - - accent-words-silicon-4 + fullReplacements: + - accent-words-silicon-1 + - accent-words-silicon-2 + - accent-words-silicon-3 + - accent-words-silicon-4 - type: accent id: xeno - words: - - accent-words-xeno-1 - - accent-words-xeno-2 - - accent-words-xeno-3 - - accent-words-xeno-4 + fullReplacements: + - accent-words-xeno-1 + - accent-words-xeno-2 + - accent-words-xeno-3 + - accent-words-xeno-4 - type: accent id: zombie - words: - - accent-words-zombie-1 - - accent-words-zombie-2 - - accent-words-zombie-3 - - accent-words-zombie-4 - - accent-words-zombie-5 - - accent-words-zombie-6 - - accent-words-zombie-7 + fullReplacements: + - accent-words-zombie-1 + - accent-words-zombie-2 + - accent-words-zombie-3 + - accent-words-zombie-4 + - accent-words-zombie-5 + - accent-words-zombie-6 + - accent-words-zombie-7 - type: accent id: genericAggressive - words: + fullReplacements: - accent-words-generic-aggressive-1 - accent-words-generic-aggressive-2 - accent-words-generic-aggressive-3 @@ -68,7 +70,7 @@ - type: accent id: duck - words: + fullReplacements: - accent-words-duck-1 - accent-words-duck-2 - accent-words-duck-3 @@ -76,7 +78,7 @@ - type: accent id: chicken - words: + fullReplacements: - accent-words-chicken-1 - accent-words-chicken-2 - accent-words-chicken-3 @@ -84,7 +86,7 @@ - type: accent id: pig - words: + fullReplacements: - accent-words-pig-1 - accent-words-pig-2 - accent-words-pig-3 diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml new file mode 100644 index 0000000000..2626bb0da7 --- /dev/null +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -0,0 +1,63 @@ +# Accents that work off of word replacements. + +# this is kind of dumb but localization demands it. +# i guess you could just specify the prefix ('mobster') and count and let the system fill it +- type: accent + id: mobster + wordReplacements: + accent-mobster-words-1: accent-mobster-words-replace-1 + accent-mobster-words-2: accent-mobster-words-replace-2 + accent-mobster-words-3: accent-mobster-words-replace-3 + accent-mobster-words-4: accent-mobster-words-replace-4 + accent-mobster-words-5: accent-mobster-words-replace-5 + accent-mobster-words-6: accent-mobster-words-replace-6 + accent-mobster-words-7: accent-mobster-words-replace-7 + accent-mobster-words-8: accent-mobster-words-replace-8 + accent-mobster-words-9: accent-mobster-words-replace-9 + accent-mobster-words-10: accent-mobster-words-replace-10 + accent-mobster-words-11: accent-mobster-words-replace-11 + accent-mobster-words-12: accent-mobster-words-replace-12 + accent-mobster-words-13: accent-mobster-words-replace-13 + accent-mobster-words-14: accent-mobster-words-replace-14 + accent-mobster-words-15: accent-mobster-words-replace-15 + accent-mobster-words-16: accent-mobster-words-replace-16 + accent-mobster-words-17: accent-mobster-words-replace-17 + +- type: accent + id: dwarf + wordReplacements: + accent-dwarf-words-1: accent-dwarf-words-replace-1 + accent-dwarf-words-2: accent-dwarf-words-replace-2 + accent-dwarf-words-3: accent-dwarf-words-replace-3 + accent-dwarf-words-4: accent-dwarf-words-replace-4 + accent-dwarf-words-5: accent-dwarf-words-replace-5 + accent-dwarf-words-6: accent-dwarf-words-replace-6 + accent-dwarf-words-7: accent-dwarf-words-replace-7 + accent-dwarf-words-8: accent-dwarf-words-replace-8 + accent-dwarf-words-9: accent-dwarf-words-replace-9 + accent-dwarf-words-10: accent-dwarf-words-replace-10 + accent-dwarf-words-11: accent-dwarf-words-replace-11 + accent-dwarf-words-12: accent-dwarf-words-replace-12 + accent-dwarf-words-13: accent-dwarf-words-replace-13 + accent-dwarf-words-14: accent-dwarf-words-replace-14 + accent-dwarf-words-15: accent-dwarf-words-replace-15 + accent-dwarf-words-16: accent-dwarf-words-replace-16 + accent-dwarf-words-17: accent-dwarf-words-replace-17 + accent-dwarf-words-18: accent-dwarf-words-replace-18 + accent-dwarf-words-19: accent-dwarf-words-replace-19 + accent-dwarf-words-20: accent-dwarf-words-replace-20 + accent-dwarf-words-21: accent-dwarf-words-replace-21 + accent-dwarf-words-22: accent-dwarf-words-replace-22 + accent-dwarf-words-23: accent-dwarf-words-replace-23 + accent-dwarf-words-24: accent-dwarf-words-replace-24 + accent-dwarf-words-25: accent-dwarf-words-replace-25 + accent-dwarf-words-26: accent-dwarf-words-replace-26 + accent-dwarf-words-27: accent-dwarf-words-replace-27 + accent-dwarf-words-28: accent-dwarf-words-replace-28 + accent-dwarf-words-29: accent-dwarf-words-replace-29 + accent-dwarf-words-30: accent-dwarf-words-replace-30 + accent-dwarf-words-31: accent-dwarf-words-replace-31 + accent-dwarf-words-32: accent-dwarf-words-replace-32 + accent-dwarf-words-33: accent-dwarf-words-replace-33 + accent-dwarf-words-34: accent-dwarf-words-replace-34 + accent-dwarf-words-35: accent-dwarf-words-replace-35 diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index 15b226ded5..e909368124 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -42,7 +42,8 @@ Male: UnisexDwarf Female: UnisexDwarf Unsexed: UnisexDwarf - - type: DwarfAccent + - type: ReplacementAccent + accent: dwarf - type: Speech speechSounds: Bass diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index a4611f62f9..8fa3d13de6 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -311,22 +311,6 @@ binds: type: State key: BackSpace canRepeat: true -- function: TextDelete - type: State - key: Delete - canRepeat: true -- function: TextWordBackspace - type: State - key: BackSpace - mod1: Control - canRepeat: true - allowSubCombs: true -- function: TextWordDelete - type: State - key: Delete - mod1: Control - canRepeat: true - allowSubCombs: true - function: TextNewline type: State key: Return @@ -384,6 +368,10 @@ binds: - function: TextScrollToBottom type: State key: PageDown +- function: TextDelete + type: State + key: Delete + canRepeat: true - function: TextTabComplete type: State key: Tab -- 2.51.2