+++ /dev/null
-namespace Content.Server.Speech.Components;
-
-/// <summary>
-/// Used for replacing words for dwarves (pseudo-scottish)
-/// </summary>
-[RegisterComponent]
-public sealed class DwarfAccentComponent : Component
-{
-}
[IdDataField]
public string ID { get; } = default!;
- [DataField("words")]
- public string[] Words = default!;
+ /// <summary>
+ /// If this array is non-null, the full text of anything said will be randomly replaced with one of these words.
+ /// </summary>
+ [DataField("fullReplacements")]
+ public string[]? FullReplacements;
+
+ /// <summary>
+ /// If this dictionary is non-null and <see cref="FullReplacements"/> is null, any keys surrounded by spaces
+ /// (words) will be replaced by the value, attempting to intelligently keep capitalization.
+ /// </summary>
+ [DataField("wordReplacements")]
+ public Dictionary<string, string>? WordReplacements;
}
/// <summary>
- /// Replaces any spoken sentences with a random word.
+ /// Replaces full sentences or words within sentences with new strings.
/// </summary>
[RegisterComponent]
public sealed class ReplacementAccentComponent : Component
+++ /dev/null
-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<string, string> 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<DwarfAccentComponent, AccentGetEvent>(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, $@"(?<!\w){first}(?!\w)", replace, RegexOptions.IgnoreCase);
- }
-
- return msg;
- }
-
- private void OnAccentGet(EntityUid uid, DwarfAccentComponent component, AccentGetEvent args)
- {
- args.Message = Accentuate(args.Message);
- }
-}
public sealed class MobsterAccentSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ReplacementAccentSystem _replacement = default!;
private static readonly Dictionary<string, string> DirectReplacements = new()
{
// Do text manipulations first
// Then prefix/suffix funnyies
- var msg = message;
-
- foreach (var (first, replace) in DirectReplacements)
- {
- msg = Regex.Replace(msg, $@"(?<!\w){first}(?!\w)", replace, RegexOptions.IgnoreCase);
- }
+ // direct word replacements
+ var msg = _replacement.ApplyReplacements(message, "mobster");
// thinking -> thinkin'
// king -> king
+using System.Linq;
+using System.Text.RegularExpressions;
using Content.Server.Speech.Components;
+using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
{
// TODO: Code in-game languages and make this a language
/// <summary>
- /// Replaces any spoken sentences with a random word.
+ /// Replaces text in messages, either with full replacements or word replacements.
/// </summary>
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()
{
private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args)
{
- var words = _proto.Index<ReplacementAccentPrototype>(component.Accent).Words;
+ args.Message = ApplyReplacements(args.Message, component.Accent);
+ }
+
+ /// <summary>
+ /// Attempts to apply a given replacement accent prototype to a message.
+ /// </summary>
+ [PublicAPI]
+ public string ApplyReplacements(string message, string accent)
+ {
+ if (!_proto.TryIndex<ReplacementAccentPrototype>(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, $@"(?<!\w){f}(?!\w)", RegexOptions.IgnoreCase))
+ {
+ var replacement = r;
+
+ // Intelligently replace capitalization
+ // two cases where we will do so:
+ // - the string is all upper case (just uppercase the replacement too)
+ // - the first letter of the word is capitalized (common, just uppercase the first letter too)
+ // any other cases are not really useful or not viable, since the match & replacement can be different
+ // lengths
+
+ // second expression here is weird--its specifically for single-word capitalization for I or A
+ // dwarf expands I -> 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;
}
}
}
--- /dev/null
+# 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
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
+# 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
- type: accent
id: dog
- words:
+ fullReplacements:
- accent-words-dog-1
- accent-words-dog-2
- accent-words-dog-3
- type: accent
id: mouse
- words:
+ fullReplacements:
- accent-words-mouse-1
- accent-words-mouse-2
- accent-words-mouse-3
- 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
- type: accent
id: duck
- words:
+ fullReplacements:
- accent-words-duck-1
- accent-words-duck-2
- accent-words-duck-3
- type: accent
id: chicken
- words:
+ fullReplacements:
- accent-words-chicken-1
- accent-words-chicken-2
- accent-words-chicken-3
- type: accent
id: pig
- words:
+ fullReplacements:
- accent-words-pig-1
- accent-words-pig-2
- accent-words-pig-3
--- /dev/null
+# 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
Male: UnisexDwarf
Female: UnisexDwarf
Unsexed: UnisexDwarf
- - type: DwarfAccent
+ - type: ReplacementAccent
+ accent: dwarf
- type: Speech
speechSounds: Bass
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
- function: TextScrollToBottom
type: State
key: PageDown
+- function: TextDelete
+ type: State
+ key: Delete
+ canRepeat: true
- function: TextTabComplete
type: State
key: Tab