Accent trait limit (#28046)

This commit is contained in:
Ed
2024-06-03 21:47:06 +03:00
committed by GitHub
parent ee8224bce2
commit a4d1601758
14 changed files with 365 additions and 171 deletions

View File

@@ -7,6 +7,7 @@ using Content.Client.Lobby.UI.Loadouts;
using Content.Client.Lobby.UI.Roles; using Content.Client.Lobby.UI.Roles;
using Content.Client.Message; using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking; using Content.Client.Players.PlayTimeTracking;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Guidebook; using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Clothing; 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(); var traits = _prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
if (traits.Count > 0) if (traits.Count < 1)
{
foreach (var trait in traits)
{
var selector = new TraitPreferenceSelector(trait);
if (Profile?.TraitPreferences.Contains(trait.ID) == true)
{
selector.Preference = true;
}
else
{
selector.Preference = false;
}
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
SetDirty();
};
TraitsList.AddChild(selector);
}
}
else
{ {
TraitsList.AddChild(new Label TraitsList.AddChild(new Label
{ {
// TODO: Localise Text = Loc.GetString("humanoid-profile-editor-no-traits"),
Text = "No traits available :(",
FontColorOverride = Color.Gray, FontColorOverride = Color.Gray,
}); });
return;
}
//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
{
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, categoryId, preference);
SetDirty();
RefreshTraits(); // If too many traits are selected, they will be reset to the real value.
};
selectors.Add(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
});
}
foreach (var selector in selectors)
{
if (selector == null)
continue;
if (category is { MaxTraitPoints: >= 0 } &&
selector.Cost + selectionCount > category.MaxTraitPoints)
{
selector.Checkbox.Label.FontColorOverride = Color.Red;
}
TraitsList.AddChild(selector);
}
} }
} }

View File

@@ -2,6 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BoxContainer Name="Container" <BoxContainer Name="Container"
Orientation="Horizontal"> Orientation="Horizontal">
<CheckBox Name="Checkbox"/> <CheckBox Name="Checkbox" Access="Public"/>
</BoxContainer> </BoxContainer>
</Control> </Control>

View File

@@ -9,6 +9,8 @@ namespace Content.Client.Lobby.UI.Roles;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class TraitPreferenceSelector : Control public sealed partial class TraitPreferenceSelector : Control
{ {
public int Cost;
public bool Preference public bool Preference
{ {
get => Checkbox.Pressed; get => Checkbox.Pressed;
@@ -20,7 +22,12 @@ public sealed partial class TraitPreferenceSelector : Control
public TraitPreferenceSelector(TraitPrototype trait) public TraitPreferenceSelector(TraitPrototype trait)
{ {
RobustXamlLoader.Load(this); 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; Checkbox.OnToggled += OnCheckBoxToggled;
if (trait.Description is { } desc) if (trait.Description is { } desc)

View File

@@ -38,27 +38,21 @@ public sealed class TraitSystem : EntitySystem
continue; continue;
// Add all components required by the prototype // Add all components required by the prototype
foreach (var entry in traitPrototype.Components.Values) EntityManager.AddComponents(args.Mob, traitPrototype.Components, false);
{
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);
}
// Add item required by the trait // Add item required by the trait
if (traitPrototype.TraitGear != null) if (traitPrototype.TraitGear == null)
{ continue;
if (!TryComp(args.Mob, out HandsComponent? handsComponent))
continue;
var coords = Transform(args.Mob).Coordinates; if (!TryComp(args.Mob, out HandsComponent? handsComponent))
var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords); continue;
_sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false,
handsComp: handsComponent); var coords = Transform(args.Mob).Coordinates;
} var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords);
_sharedHandsSystem.TryPickup(args.Mob,
inhandEntity,
checkActionBlocker: false,
handsComp: handsComponent);
} }
} }
} }

View 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); var list = new HashSet<string>(_traitPreferences);
if (pref) if (pref)
{ {
list.Add(traitId); 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 else
{ {

View File

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

View File

@@ -1,55 +1,63 @@
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Prototypes; 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> /// <summary>
/// Describes a trait. /// The name of this trait.
/// </summary> /// </summary>
[Prototype("trait")] [DataField]
public sealed partial class TraitPrototype : IPrototype public LocId Name { get; private set; } = string.Empty;
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary> /// <summary>
/// The name of this trait. /// The description of this trait.
/// </summary> /// </summary>
[DataField("name")] [DataField]
public string Name { get; private set; } = ""; public LocId? Description { get; private set; }
/// <summary> /// <summary>
/// The description of this trait. /// Don't apply this trait to entities this whitelist IS NOT valid for.
/// </summary> /// </summary>
[DataField("description")] [DataField]
public string? Description { get; private set; } public EntityWhitelist? Whitelist;
/// <summary> /// <summary>
/// Don't apply this trait to entities this whitelist IS NOT valid for. /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist)
/// </summary> /// </summary>
[DataField("whitelist")] [DataField]
public EntityWhitelist? Whitelist; public EntityWhitelist? Blacklist;
/// <summary> /// <summary>
/// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist) /// The components that get added to the player, when they pick this trait.
/// </summary> /// </summary>
[DataField("blacklist")] [DataField]
public EntityWhitelist? Blacklist; public ComponentRegistry Components { get; private set; } = default!;
/// <summary> /// <summary>
/// The components that get added to the player, when they pick this trait. /// Gear that is given to the player, when they pick this trait.
/// </summary> /// </summary>
[DataField("components")] [DataField]
public ComponentRegistry Components { get; private set; } = default!; public EntProtoId? TraitGear;
/// <summary> /// <summary>
/// Gear that is given to the player, when they pick this trait. /// Trait Price. If negative number, points will be added.
/// </summary> /// </summary>
[DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))] [DataField]
public string? TraitGear; 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;
} }

View File

@@ -42,7 +42,7 @@ humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
humanoid-profile-editor-antags-tab = Antags humanoid-profile-editor-antags-tab = Antags
humanoid-profile-editor-antag-preference-yes-button = Yes humanoid-profile-editor-antag-preference-yes-button = Yes
humanoid-profile-editor-antag-preference-no-button = No 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-high-button = High
humanoid-profile-editor-job-priority-medium-button = Medium humanoid-profile-editor-job-priority-medium-button = Medium
humanoid-profile-editor-job-priority-low-button = Low 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-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-markings-tab = Markings
humanoid-profile-editor-flavortext-tab = Description 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

View 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] 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-lightweight-desc = Alcohol has a stronger effect on you
trait-muted-name = Muted 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-name = Unrevivable
trait-unrevivable-desc = You are unable to be revived by defibrillators. 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-pirate-accent-desc = You can't stop speaking like a pirate!
trait-accentless-name = Accentless trait-accentless-name = Accentless
trait-accentless-desc = You don't have the accent that your species would usually have 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-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-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-southern-desc = You have a different way of speakin'.
trait-snoring-name = Snoring trait-snoring-name = Snoring
trait-snoring-desc = You will snore while sleeping. trait-snoring-desc = You will snore while sleeping.
trait-liar-name = Pathological liar trait-liar-name = Pathological liar
trait-liar-desc = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. 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!

View File

@@ -0,0 +1,8 @@
- type: traitCategory
id: Disabilities
name: trait-category-disabilities
- type: traitCategory
id: SpeechTraits
name: trait-category-speech
maxTraitPoints: 2

View File

@@ -3,6 +3,7 @@
name: trait-blindness-name name: trait-blindness-name
description: trait-blindness-desc description: trait-blindness-desc
traitGear: WhiteCane traitGear: WhiteCane
category: Disabilities
whitelist: whitelist:
components: components:
- Blindable - Blindable
@@ -14,6 +15,7 @@
name: trait-poor-vision-name name: trait-poor-vision-name
description: trait-poor-vision-desc description: trait-poor-vision-desc
traitGear: ClothingEyesGlasses traitGear: ClothingEyesGlasses
category: Disabilities
whitelist: whitelist:
components: components:
- Blindable - Blindable
@@ -25,6 +27,7 @@
id: Narcolepsy id: Narcolepsy
name: trait-narcolepsy-name name: trait-narcolepsy-name
description: trait-narcolepsy-desc description: trait-narcolepsy-desc
category: Disabilities
components: components:
- type: Narcolepsy - type: Narcolepsy
timeBetweenIncidents: 300, 600 timeBetweenIncidents: 300, 600
@@ -34,13 +37,43 @@
id: Pacifist id: Pacifist
name: trait-pacifist-name name: trait-pacifist-name
description: trait-pacifist-desc description: trait-pacifist-desc
category: Disabilities
components: components:
- type: Pacified - type: Pacified
- type: trait
id: Unrevivable
name: trait-unrevivable-name
description: trait-unrevivable-desc
category: Disabilities
components:
- type: Unrevivable
- type: trait
id: Muted
name: trait-muted-name
description: trait-muted-desc
category: Disabilities
blacklist:
components:
- BorgChassis
components:
- type: Muted
- type: trait
id: LightweightDrunk
name: trait-lightweight-name
description: trait-lightweight-desc
category: Disabilities
components:
- type: LightweightDrunk
boozeStrengthMultiplier: 2
- type: trait - type: trait
id: Paracusia id: Paracusia
name: trait-paracusia-name name: trait-paracusia-name
description: trait-paracusia-desc description: trait-paracusia-desc
category: Disabilities
components: components:
- type: Paracusia - type: Paracusia
minTimeBetweenIncidents: 0.1 minTimeBetweenIncidents: 0.1
@@ -49,33 +82,10 @@
sounds: sounds:
collection: Paracusia collection: Paracusia
- type: trait
id: Unrevivable
name: trait-unrevivable-name
description: trait-unrevivable-desc
components:
- type: Unrevivable
- type: trait
id: Muted
name: trait-muted-name
description: trait-muted-desc
blacklist:
components:
- BorgChassis
components:
- type: Muted
- type: trait
id: FrontalLisp
name: trait-frontal-lisp-name
description: trait-frontal-lisp-desc
components:
- type: FrontalLisp
- type: trait - type: trait
id: Snoring id: Snoring
name: trait-snoring-name name: trait-snoring-name
description: trait-snoring-desc description: trait-snoring-desc
category: Disabilities
components: components:
- type: Snoring - type: Snoring

View File

@@ -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

View File

@@ -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

View File

@@ -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