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;
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);
+ }
}
}
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BoxContainer Name="Container"
Orientation="Horizontal">
- <CheckBox Name="Checkbox"/>
+ <CheckBox Name="Checkbox" Access="Public"/>
</BoxContainer>
</Control>
[GenerateTypedNameReferences]
public sealed partial class TraitPreferenceSelector : Control
{
+ public int Cost;
+
public bool Preference
{
get => Checkbox.Pressed;
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)
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);
}
}
}
};
}
- 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
{
--- /dev/null
+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;
+}
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;
}
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
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
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
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
--- /dev/null
+- type: traitCategory
+ id: Disabilities
+ name: trait-category-disabilities
+
+- type: traitCategory
+ id: SpeechTraits
+ name: trait-category-speech
+ maxTraitPoints: 2
name: trait-blindness-name
description: trait-blindness-desc
traitGear: WhiteCane
+ category: Disabilities
whitelist:
components:
- Blindable
name: trait-poor-vision-name
description: trait-poor-vision-desc
traitGear: ClothingEyesGlasses
+ category: Disabilities
whitelist:
components:
- Blindable
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
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
+++ /dev/null
-- 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
+++ /dev/null
-- 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
--- /dev/null
+# 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