]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Accent trait limit (#28046)
authorEd <96445749+TheShuEd@users.noreply.github.com>
Mon, 3 Jun 2024 18:47:06 +0000 (21:47 +0300)
committerGitHub <noreply@github.com>
Mon, 3 Jun 2024 18:47:06 +0000 (14:47 -0400)
14 files changed:
Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml
Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs
Content.Server/Traits/TraitSystem.cs
Content.Shared/Preferences/HumanoidCharacterProfile.cs
Content.Shared/Traits/TraitCategoryPrototype.cs [new file with mode: 0644]
Content.Shared/Traits/TraitPrototype.cs
Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
Resources/Locale/en-US/traits/traits.ftl
Resources/Prototypes/Traits/categories.yml [new file with mode: 0644]
Resources/Prototypes/Traits/disabilities.yml
Resources/Prototypes/Traits/inconveniences.yml [deleted file]
Resources/Prototypes/Traits/neutral.yml [deleted file]
Resources/Prototypes/Traits/speech.yml [new file with mode: 0644]

index 53c332c1857f0a3f2a4a60b1df722801ab2db1f9..ac43fa11a8534c3308ff9f629374af2956d5a800 100644 (file)
@@ -7,6 +7,7 @@ using Content.Client.Lobby.UI.Loadouts;
 using Content.Client.Lobby.UI.Roles;
 using Content.Client.Message;
 using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Stylesheets;
 using Content.Client.UserInterface.Systems.Guidebook;
 using Content.Shared.CCVar;
 using Content.Shared.Clothing;
@@ -466,38 +467,96 @@ namespace Content.Client.Lobby.UI
             var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
             TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
 
-            if (traits.Count > 0)
+            if (traits.Count < 1)
             {
-                foreach (var trait in traits)
+                TraitsList.AddChild(new Label
                 {
-                    var selector = new TraitPreferenceSelector(trait);
+                    Text = Loc.GetString("humanoid-profile-editor-no-traits"),
+                    FontColorOverride = Color.Gray,
+                });
+                return;
+            }
 
-                    if (Profile?.TraitPreferences.Contains(trait.ID) == true)
-                    {
-                        selector.Preference = true;
-                    }
-                    else
+            //Setup model
+            Dictionary<string, List<string>> model = new();
+            List<string> defaultTraits = new();
+            model.Add("default", defaultTraits);
+
+            foreach (var trait in traits)
+            {
+                if (trait.Category == null)
+                {
+                    defaultTraits.Add(trait.ID);
+                    continue;
+                }
+
+                if (!model.ContainsKey(trait.Category))
+                {
+                    model.Add(trait.Category, new());
+                }
+                model[trait.Category].Add(trait.ID);
+            }
+
+            //Create UI view from model
+            foreach (var (categoryId, traitId) in model)
+            {
+                TraitCategoryPrototype? category = null;
+                if (categoryId != "default")
+                {
+                    category = _prototypeManager.Index<TraitCategoryPrototype>(categoryId);
+                    // Label
+                    TraitsList.AddChild(new Label
                     {
-                        selector.Preference = false;
-                    }
+                        Text = Loc.GetString(category.Name),
+                        Margin = new Thickness(0, 10, 0, 0),
+                        StyleClasses = { StyleBase.StyleClassLabelHeading },
+                    });
+                }
+
+                List<TraitPreferenceSelector?> selectors = new();
+                var selectionCount = 0;
+
+                foreach (var traitProto in traitId)
+                {
+                    var trait = _prototypeManager.Index<TraitPrototype>(traitProto);
+                    var selector = new TraitPreferenceSelector(trait);
+
+                    selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true;
+                    if (selector.Preference)
+                        selectionCount += trait.Cost;
 
                     selector.PreferenceChanged += preference =>
                     {
-                        Profile = Profile?.WithTraitPreference(trait.ID, preference);
+                        Profile = Profile?.WithTraitPreference(trait.ID, categoryId, preference);
                         SetDirty();
+                        RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
                     };
+                    selectors.Add(selector);
+                }
 
-                    TraitsList.AddChild(selector);
+                // Selection counter
+                if (category is { MaxTraitPoints: >= 0 })
+                {
+                    TraitsList.AddChild(new Label
+                    {
+                        Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)),
+                        FontColorOverride = Color.Gray
+                    });
                 }
-            }
-            else
-            {
-                TraitsList.AddChild(new Label
+
+                foreach (var selector in selectors)
                 {
-                    // TODO: Localise
-                    Text = "No traits available :(",
-                    FontColorOverride = Color.Gray,
-                });
+                    if (selector == null)
+                        continue;
+
+                    if (category is { MaxTraitPoints: >= 0 } &&
+                        selector.Cost + selectionCount > category.MaxTraitPoints)
+                    {
+                        selector.Checkbox.Label.FontColorOverride = Color.Red;
+                    }
+
+                    TraitsList.AddChild(selector);
+                }
             }
         }
 
index 18dabe309041656ecd8a4caf1d1f4fc9a51122f7..266b4b8eeee45e804cb998f7427f83afe79a65b3 100644 (file)
@@ -2,6 +2,6 @@
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
         <BoxContainer Name="Container"
                       Orientation="Horizontal">
-            <CheckBox Name="Checkbox"/>
+            <CheckBox Name="Checkbox" Access="Public"/>
         </BoxContainer>
 </Control>
index 498a5ca4e504f35a74147e349047dbe950e0fc2d..a52a3fa2dbc3d5d8a2a0d34774ed2f2a1d2d759d 100644 (file)
@@ -9,6 +9,8 @@ namespace Content.Client.Lobby.UI.Roles;
 [GenerateTypedNameReferences]
 public sealed partial class TraitPreferenceSelector : Control
 {
+    public int Cost;
+
     public bool Preference
     {
         get => Checkbox.Pressed;
@@ -20,7 +22,12 @@ public sealed partial class TraitPreferenceSelector : Control
     public TraitPreferenceSelector(TraitPrototype trait)
     {
         RobustXamlLoader.Load(this);
-        Checkbox.Text = Loc.GetString(trait.Name);
+
+        var text = trait.Cost != 0 ? $"[{trait.Cost}] " : "";
+        text += Loc.GetString(trait.Name);
+
+        Cost = trait.Cost;
+        Checkbox.Text = text;
         Checkbox.OnToggled += OnCheckBoxToggled;
 
         if (trait.Description is { } desc)
index 22ee0e4861f4eaa1f0404ee0b0dbd192a7f474e7..f7531f7e2ce70dda8fb672965eba14c9d747d721 100644 (file)
@@ -38,27 +38,21 @@ public sealed class TraitSystem : EntitySystem
                 continue;
 
             // Add all components required by the prototype
-            foreach (var entry in traitPrototype.Components.Values)
-            {
-                if (HasComp(args.Mob, entry.Component.GetType()))
-                    continue;
-
-                var comp = (Component) _serializationManager.CreateCopy(entry.Component, notNullableOverride: true);
-                comp.Owner = args.Mob;
-                EntityManager.AddComponent(args.Mob, comp);
-            }
+            EntityManager.AddComponents(args.Mob, traitPrototype.Components, false);
 
             // Add item required by the trait
-            if (traitPrototype.TraitGear != null)
-            {
-                if (!TryComp(args.Mob, out HandsComponent? handsComponent))
-                    continue;
+            if (traitPrototype.TraitGear == null)
+                continue;
 
-                var coords = Transform(args.Mob).Coordinates;
-                var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords);
-                _sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false,
-                    handsComp: handsComponent);
-            }
+            if (!TryComp(args.Mob, out HandsComponent? handsComponent))
+                continue;
+
+            var coords = Transform(args.Mob).Coordinates;
+            var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords);
+            _sharedHandsSystem.TryPickup(args.Mob,
+                inhandEntity,
+                checkActionBlocker: false,
+                handsComp: handsComponent);
         }
     }
 }
index f47a3ac3dbe71f3e60e16460c8f1913eb8e81adb..bd55bbb40a8ffce82a5cf879757fbc3aff16d0f8 100644 (file)
@@ -346,13 +346,43 @@ namespace Content.Shared.Preferences
             };
         }
 
-        public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
+        public HumanoidCharacterProfile WithTraitPreference(string traitId, string? categoryId, bool pref)
         {
+            var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
+            var traitProto = prototypeManager.Index<TraitPrototype>(traitId);
+
+            TraitCategoryPrototype? categoryProto = null;
+            if (categoryId != null && categoryId != "default")
+                categoryProto = prototypeManager.Index<TraitCategoryPrototype>(categoryId);
+
             var list = new HashSet<string>(_traitPreferences);
 
             if (pref)
             {
                 list.Add(traitId);
+
+                if (categoryProto == null || categoryProto.MaxTraitPoints < 0)
+                {
+                    return new(this)
+                    {
+                        _traitPreferences = list,
+                    };
+                }
+
+                var count = 0;
+                foreach (var trait in list)
+                {
+                    var traitProtoTemp = prototypeManager.Index<TraitPrototype>(trait);
+                    count += traitProtoTemp.Cost;
+                }
+
+                if (count > categoryProto.MaxTraitPoints && traitProto.Cost != 0)
+                {
+                    return new(this)
+                    {
+                        _traitPreferences = _traitPreferences,
+                    };
+                }
             }
             else
             {
diff --git a/Content.Shared/Traits/TraitCategoryPrototype.cs b/Content.Shared/Traits/TraitCategoryPrototype.cs
new file mode 100644 (file)
index 0000000..1da6241
--- /dev/null
@@ -0,0 +1,26 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Traits;
+
+/// <summary>
+/// Traits category with general settings. Allows you to limit the number of taken traits in one category
+/// </summary>
+[Prototype]
+public sealed partial class TraitCategoryPrototype : IPrototype
+{
+    [ViewVariables]
+    [IdDataField]
+    public string ID { get; private set; } = default!;
+
+    /// <summary>
+    ///     Name of the trait category displayed in the UI
+    /// </summary>
+    [DataField]
+    public LocId Name { get; private set; } = string.Empty;
+
+    /// <summary>
+    ///     The maximum number of traits that can be taken in this category. If -1, you can take as many traits as you like.
+    /// </summary>
+    [DataField]
+    public int MaxTraitPoints = -1;
+}
index 34feb8da22c37163ae618060ddb945449deafc9c..c79d3cbf308e18a1bcdce6c05e64f4f31b74ed19 100644 (file)
@@ -1,55 +1,63 @@
 using Content.Shared.Whitelist;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
-// don't worry about it
+namespace Content.Shared.Traits;
 
-namespace Content.Shared.Traits
+/// <summary>
+/// Describes a trait.
+/// </summary>
+[Prototype]
+public sealed partial class TraitPrototype : IPrototype
 {
+    [ViewVariables]
+    [IdDataField]
+    public string ID { get; private set; } = default!;
+
+    /// <summary>
+    /// The name of this trait.
+    /// </summary>
+    [DataField]
+    public LocId Name { get; private set; } = string.Empty;
+
+    /// <summary>
+    /// The description of this trait.
+    /// </summary>
+    [DataField]
+    public LocId? Description { get; private set; }
+
+    /// <summary>
+    /// Don't apply this trait to entities this whitelist IS NOT valid for.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Whitelist;
+
     /// <summary>
-    ///     Describes a trait.
-    /// </summary>
-    [Prototype("trait")]
-    public sealed partial class TraitPrototype : IPrototype
-    {
-        [ViewVariables]
-        [IdDataField]
-        public string ID { get; private set; } = default!;
-
-        /// <summary>
-        ///     The name of this trait.
-        /// </summary>
-        [DataField("name")]
-        public string Name { get; private set; } = "";
-
-        /// <summary>
-        ///     The description of this trait.
-        /// </summary>
-        [DataField("description")]
-        public string? Description { get; private set; }
-
-        /// <summary>
-        ///     Don't apply this trait to entities this whitelist IS NOT valid for.
-        /// </summary>
-        [DataField("whitelist")]
-        public EntityWhitelist? Whitelist;
-
-        /// <summary>
-        ///     Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist)
-        /// </summary>
-        [DataField("blacklist")]
-        public EntityWhitelist? Blacklist;
-
-        /// <summary>
-        ///     The components that get added to the player, when they pick this trait.
-        /// </summary>
-        [DataField("components")]
-        public ComponentRegistry Components { get; private set; } = default!;
-
-        /// <summary>
-        ///     Gear that is given to the player, when they pick this trait.
-        /// </summary>
-        [DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
-        public string? TraitGear;
-    }
+    /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist)
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Blacklist;
+
+    /// <summary>
+    /// The components that get added to the player, when they pick this trait.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry Components { get; private set; } = default!;
+
+    /// <summary>
+    /// Gear that is given to the player, when they pick this trait.
+    /// </summary>
+    [DataField]
+    public EntProtoId? TraitGear;
+
+    /// <summary>
+    /// Trait Price. If negative number, points will be added.
+    /// </summary>
+    [DataField]
+    public int Cost = 0;
+
+    /// <summary>
+    /// Adds a trait to a category, allowing you to limit the selection of some traits to the settings of that category.
+    /// </summary>
+    [DataField]
+    public ProtoId<TraitCategoryPrototype>? Category;
 }
index c7a24d540585aee0878a90c07d67627bab25fe6f..bfdbeb2f140b10487d15d33991970db9e8229ad2 100644 (file)
@@ -42,7 +42,7 @@ humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
 humanoid-profile-editor-antags-tab = Antags
 humanoid-profile-editor-antag-preference-yes-button = Yes
 humanoid-profile-editor-antag-preference-no-button = No
-humanoid-profile-editor-traits-tab = Traits
+
 humanoid-profile-editor-job-priority-high-button = High
 humanoid-profile-editor-job-priority-medium-button = Medium
 humanoid-profile-editor-job-priority-low-button = Low
@@ -50,3 +50,12 @@ humanoid-profile-editor-job-priority-never-button = Never
 humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC names and descriptions will lead to admin intervention on this server. Read our \[Rules\] for more.
 humanoid-profile-editor-markings-tab = Markings
 humanoid-profile-editor-flavortext-tab = Description
+
+# Traits
+humanoid-profile-editor-traits-tab = Traits
+humanoid-profile-editor-no-traits = No traits available
+
+humanoid-profile-editor-trait-count-hint = Points available: [{$current}/{$max}]
+
+trait-category-disabilities = Disabilities
+trait-category-speech = Speech traits
\ No newline at end of file
index cbf65308f3ea4e087fad5696f5af5873b4dd1399..e3aed1c8b246ce441cf7e3bcaa9b18d69fb12677 100644 (file)
@@ -12,7 +12,7 @@ trait-pacifist-desc = You cannot attack or hurt any living beings.
 
 permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color]
 
-trait-lightweight-name = Lightweight Drunk
+trait-lightweight-name = Lightweight drunk
 trait-lightweight-desc = Alcohol has a stronger effect on you
 
 trait-muted-name = Muted
@@ -24,23 +24,29 @@ trait-paracusia-desc = You hear sounds that aren't really there
 trait-unrevivable-name = Unrevivable
 trait-unrevivable-desc = You are unable to be revived by defibrillators.
 
-trait-pirate-accent-name = Pirate Accent
+trait-pirate-accent-name = Pirate accent
 trait-pirate-accent-desc = You can't stop speaking like a pirate!
 
 trait-accentless-name = Accentless
 trait-accentless-desc = You don't have the accent that your species would usually have
 
-trait-frontal-lisp-name = Frontal Lisp
+trait-frontal-lisp-name = Frontal lisp
 trait-frontal-lisp-desc = You thpeak with a lithp
 
-trait-socialanxiety-name = Social Anxiety
+trait-socialanxiety-name = Social anxiety
 trait-socialanxiety-desc = You are anxious when you speak and stutter.
 
-trait-southern-name = Southern Drawl
+trait-southern-name = Southern drawl
 trait-southern-desc = You have a different way of speakin'.
 
 trait-snoring-name = Snoring
 trait-snoring-desc = You will snore while sleeping.
 
 trait-liar-name = Pathological liar
-trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway.
\ No newline at end of file
+trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway.
+
+trait-cowboy-name = Cowboy accent
+trait-cowboy-desc = You speak with a distinct cowboy accent!
+
+trait-italian-name = Italian accent
+trait-italian-desc = Mamma mia! You seem to have lived in space italy!
\ No newline at end of file
diff --git a/Resources/Prototypes/Traits/categories.yml b/Resources/Prototypes/Traits/categories.yml
new file mode 100644 (file)
index 0000000..a362164
--- /dev/null
@@ -0,0 +1,8 @@
+- type: traitCategory
+  id: Disabilities
+  name: trait-category-disabilities
+
+- type: traitCategory
+  id: SpeechTraits
+  name: trait-category-speech
+  maxTraitPoints: 2
index be1e981549c6a2a518b0fddfe80454d866515325..6e0026e44ebd306abf558757fb64f3c91cd47616 100644 (file)
@@ -3,6 +3,7 @@
   name: trait-blindness-name
   description: trait-blindness-desc
   traitGear: WhiteCane
+  category: Disabilities
   whitelist:
     components:
       - Blindable
@@ -14,6 +15,7 @@
   name: trait-poor-vision-name
   description: trait-poor-vision-desc
   traitGear: ClothingEyesGlasses
+  category: Disabilities
   whitelist:
     components:
       - Blindable
@@ -25,6 +27,7 @@
   id: Narcolepsy
   name: trait-narcolepsy-name
   description: trait-narcolepsy-desc
+  category: Disabilities
   components:
     - type: Narcolepsy
       timeBetweenIncidents: 300, 600
   id: Pacifist
   name: trait-pacifist-name
   description: trait-pacifist-desc
+  category: Disabilities
   components:
     - type: Pacified
 
-- type: trait
-  id: Paracusia
-  name: trait-paracusia-name
-  description: trait-paracusia-desc
-  components:
-    - type: Paracusia
-      minTimeBetweenIncidents: 0.1
-      maxTimeBetweenIncidents: 300
-      maxSoundDistance: 7
-      sounds:
-        collection: Paracusia
-
 - type: trait
   id: Unrevivable
   name: trait-unrevivable-name
   description: trait-unrevivable-desc
+  category: Disabilities
   components:
     - type: Unrevivable
 
@@ -60,6 +53,7 @@
   id: Muted
   name: trait-muted-name
   description: trait-muted-desc
+  category: Disabilities
   blacklist:
     components:
       - BorgChassis
     - type: Muted
 
 - type: trait
-  id: FrontalLisp
-  name: trait-frontal-lisp-name
-  description: trait-frontal-lisp-desc
+  id: LightweightDrunk
+  name: trait-lightweight-name
+  description: trait-lightweight-desc
+  category: Disabilities
   components:
-    - type: FrontalLisp
+    - type: LightweightDrunk
+      boozeStrengthMultiplier: 2
+
+- type: trait
+  id: Paracusia
+  name: trait-paracusia-name
+  description: trait-paracusia-desc
+  category: Disabilities
+  components:
+    - type: Paracusia
+      minTimeBetweenIncidents: 0.1
+      maxTimeBetweenIncidents: 300
+      maxSoundDistance: 7
+      sounds:
+        collection: Paracusia
 
 - type: trait
   id: Snoring
   name: trait-snoring-name
   description: trait-snoring-desc
+  category: Disabilities
   components:
     - type: Snoring
diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml
deleted file mode 100644 (file)
index 657781d..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-- type: trait
-  id: LightweightDrunk
-  name: trait-lightweight-name
-  description: trait-lightweight-desc
-  components:
-    - type: LightweightDrunk
-      boozeStrengthMultiplier: 2
-
-- type: trait
-  id: SocialAnxiety
-  name: trait-socialanxiety-name
-  description: trait-socialanxiety-desc
-  components:
-    - type: StutteringAccent
-      matchRandomProb: 0.1
-      fourRandomProb: 0
-      threeRandomProb: 0
-      cutRandomProb: 0
diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml
deleted file mode 100644 (file)
index 78d2bba..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-- type: trait
-  id: PirateAccent
-  name: trait-pirate-accent-name
-  description: trait-pirate-accent-desc
-  components:
-    - type: PirateAccent
-
-- type: trait
-  id: Accentless
-  name: trait-accentless-name
-  description: trait-accentless-desc
-  components:
-  - type: Accentless
-    removes:
-    - type: LizardAccent
-    - type: MothAccent
-    - type: ReplacementAccent
-      accent: dwarf
-
-- type: trait
-  id: Southern
-  name: trait-southern-name
-  description: trait-southern-desc
-  components:
-    - type: SouthernAccent
-
-- type: trait
-  id: Liar
-  name: trait-liar-name
-  description: trait-liar-desc
-  components:
-    - type: ReplacementAccent
-      accent: liar
diff --git a/Resources/Prototypes/Traits/speech.yml b/Resources/Prototypes/Traits/speech.yml
new file mode 100644 (file)
index 0000000..9448e16
--- /dev/null
@@ -0,0 +1,88 @@
+# Free
+
+- type: trait
+  id: Accentless
+  name: trait-accentless-name
+  description: trait-accentless-desc
+  category: SpeechTraits
+  components:
+  - type: Accentless
+    removes:
+    - type: LizardAccent
+    - type: MothAccent
+    - type: ReplacementAccent
+      accent: dwarf
+
+# 1 Cost
+
+- type: trait
+  id: SouthernAccent
+  name: trait-southern-name
+  description: trait-southern-desc
+  category: SpeechTraits
+  cost: 1
+  components:
+    - type: SouthernAccent
+
+- type: trait
+  id: PirateAccent
+  name: trait-pirate-accent-name
+  description: trait-pirate-accent-desc
+  category: SpeechTraits
+  cost: 1
+  components:
+    - type: PirateAccent
+
+- type: trait
+  id: CowboyAccent
+  name: trait-cowboy-name
+  description: trait-cowboy-desc
+  category: SpeechTraits
+  cost: 1
+  components:
+    - type: ReplacementAccent
+      accent: cowboy
+
+- type: trait
+  id: ItalianAccent
+  name: trait-italian-name
+  description: trait-italian-desc
+  category: SpeechTraits
+  cost: 1
+  components:
+    - type: ReplacementAccent
+      accent: italian
+
+- type: trait
+  id: Liar
+  name: trait-liar-name
+  description: trait-liar-desc
+  category: SpeechTraits
+  cost: 1
+  components:
+    - type: ReplacementAccent
+      accent: liar
+
+# 2 Cost
+
+- type: trait
+  id: SocialAnxiety
+  name: trait-socialanxiety-name
+  description: trait-socialanxiety-desc
+  category: SpeechTraits
+  cost: 2
+  components:
+    - type: StutteringAccent
+      matchRandomProb: 0.1
+      fourRandomProb: 0
+      threeRandomProb: 0
+      cutRandomProb: 0
+
+- type: trait
+  id: FrontalLisp
+  name: trait-frontal-lisp-name
+  description: trait-frontal-lisp-desc
+  category: SpeechTraits
+  cost: 2
+  components:
+    - type: FrontalLisp