Implement traits system (#10693)

This commit is contained in:
Visne
2022-09-10 17:40:06 +02:00
committed by GitHub
parent e1782ec22b
commit 4cc5fa239e
17 changed files with 3006 additions and 29 deletions

View File

@@ -2,8 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:magicmirror="clr-namespace:Content.Client.CharacterAppearance"
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
xmlns:markings="clr-namespace:Content.Client.Markings"
xmlns:flavorText="clr-namespace:Content.Client.FlavorText">
xmlns:markings="clr-namespace:Content.Client.Markings">
<BoxContainer Orientation="Horizontal">
<!-- Left side -->
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
@@ -47,6 +46,7 @@
</prefUi:HighlightedContainer>
</BoxContainer>
</BoxContainer>
<Control MinHeight="10" />
<!-- tabContainer -->
<TabContainer Name="CTabContainer" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
@@ -133,13 +133,19 @@
<BoxContainer Name="CJobList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical" Margin="10">
<!-- Antags -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="CAntagList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Name="CMarkingsTab" Orientation="Vertical">
<BoxContainer Orientation="Vertical" Margin="10">
<!-- Traits -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="CTraitsList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Name="CMarkingsTab" Orientation="Vertical" Margin="10">
<!-- Markings -->
<ScrollContainer VerticalExpand="True">
<markings:MarkingPicker Name="CMarkings" />

View File

@@ -15,6 +15,7 @@ using Content.Shared.Markings;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Species;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -80,12 +81,14 @@ namespace Content.Client.Preferences.UI
private TabContainer _tabContainer => CTabContainer;
private BoxContainer _jobList => CJobList;
private BoxContainer _antagList => CAntagList;
private BoxContainer _traitsList => CTraitsList;
private readonly List<JobPrioritySelector> _jobPriorities;
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
private readonly Dictionary<string, BoxContainer> _jobCategories;
// Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
private readonly List<SpeciesPrototype> _speciesList;
private readonly List<AntagPreferenceSelector> _antagPreferences;
private readonly List<TraitPreferenceSelector> _traitPreferences;
private Control _previewSpriteControl => CSpriteViewFront;
private Control _previewSpriteSideControl => CSpriteViewSide;
@@ -461,14 +464,46 @@ namespace Content.Client.Preferences.UI
#endregion Antags
#region Traits
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => t.Name).ToList();
_traitPreferences = new List<TraitPreferenceSelector>();
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
if (traits.Count > 0)
{
foreach (var trait in traits)
{
var selector = new TraitPreferenceSelector(trait);
_traitsList.AddChild(selector);
_traitPreferences.Add(selector);
selector.PreferenceChanged += preference =>
{
Profile = Profile?.WithTraitPreference(trait.ID, preference);
IsDirty = true;
};
}
}
else
{
_traitsList.AddChild(new Label
{
Text = "No traits available :(",
FontColorOverride = Color.Gray,
});
}
#endregion
#region Save
_saveButton.OnPressed += args => { Save(); };
_saveButton.OnPressed += _ => { Save(); };
#endregion Save
#region Markings
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-markings-tab"));
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
CMarkings.OnMarkingAdded += OnMarkingChange;
CMarkings.OnMarkingRemoved += OnMarkingChange;
@@ -960,6 +995,7 @@ namespace Content.Client.Preferences.UI
UpdateSaveButton();
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateMarkings();
NeedsDummyRebuild = true;
@@ -1103,6 +1139,17 @@ namespace Content.Client.Preferences.UI
}
}
private void UpdateTraitPreferences()
{
foreach (var preferenceSelector in _traitPreferences)
{
var traitId = preferenceSelector.Trait.ID;
var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
preferenceSelector.Preference = preference;
}
}
private sealed class AntagPreferenceSelector : Control
{
public AntagPrototype Antag { get; }
@@ -1138,5 +1185,38 @@ namespace Content.Client.Preferences.UI
PreferenceChanged?.Invoke(Preference);
}
}
private sealed class TraitPreferenceSelector : Control
{
public TraitPrototype Trait { get; }
private readonly CheckBox _checkBox;
public bool Preference
{
get => _checkBox.Pressed;
set => _checkBox.Pressed = value;
}
public event Action<bool>? PreferenceChanged;
public TraitPreferenceSelector(TraitPrototype trait)
{
Trait = trait;
_checkBox = new CheckBox {Text = $"{trait.Name}"};
_checkBox.OnToggled += OnCheckBoxToggled;
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children = { _checkBox },
});
}
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
{
PreferenceChanged?.Invoke(Preference);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
public partial class Traits : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "trait",
columns: table => new
{
trait_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
profile_id = table.Column<int>(type: "integer", nullable: false),
trait_name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_trait", x => x.trait_id);
table.ForeignKey(
name: "FK_trait_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_trait_profile_id",
table: "trait",
column: "profile_id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "trait");
}
}
}

View File

@@ -914,6 +914,33 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("server_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("trait_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.Property<string>("TraitName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("trait_name");
b.HasKey("Id")
.HasName("PK_trait");
b.HasIndex("ProfileId")
.HasDatabaseName("IX_trait_profile_id");
b.ToTable("trait", (string)null);
});
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
{
b.Property<int>("Id")
@@ -1197,6 +1224,18 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Traits")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_trait_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("PlayerRound", b =>
{
b.HasOne("Content.Server.Database.Player", null)
@@ -1261,6 +1300,8 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Antags");
b.Navigation("Jobs");
b.Navigation("Traits");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class Traits : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "trait",
columns: table => new
{
trait_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
trait_name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_trait", x => x.trait_id);
table.ForeignKey(
name: "FK_trait_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_trait_profile_id",
table: "trait",
column: "profile_id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "trait");
}
}
}

View File

@@ -852,6 +852,31 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("server_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("trait_id");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.Property<string>("TraitName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("trait_name");
b.HasKey("Id")
.HasName("PK_trait");
b.HasIndex("ProfileId")
.HasDatabaseName("IX_trait_profile_id");
b.ToTable("trait", (string)null);
});
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
{
b.Property<int>("Id")
@@ -1133,6 +1158,18 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.Trait", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Traits")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_trait_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("PlayerRound", b =>
{
b.HasOne("Content.Server.Database.Player", null)
@@ -1197,6 +1234,8 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Antags");
b.Navigation("Jobs");
b.Navigation("Traits");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>

View File

@@ -51,6 +51,10 @@ namespace Content.Server.Database
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName})
.IsUnique();
modelBuilder.Entity<Trait>()
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
.IsUnique();
modelBuilder.Entity<Job>()
.HasIndex(j => j.ProfileId);
@@ -218,6 +222,7 @@ namespace Content.Server.Database
public string Backpack { get; set; } = null!;
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
@@ -253,6 +258,15 @@ namespace Content.Server.Database
public string AntagName { get; set; } = null!;
}
public class Trait
{
public int Id { get; set; }
public Profile Profile { get; set; } = null!;
public int ProfileId { get; set; }
public string TraitName { get; set; } = null!;
}
public enum DbPreferenceUnavailableMode
{
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.

View File

@@ -27,6 +27,7 @@ namespace Content.Server.Database
.Preference
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
.AsSingleQuery()
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
@@ -156,6 +157,7 @@ namespace Content.Server.Database
{
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
var antags = profile.Antags.Select(a => a.AntagName);
var traits = profile.Traits.Select(t => t.TraitName);
var sex = Sex.Male;
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
@@ -211,7 +213,8 @@ namespace Content.Server.Database
backpack,
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
antags.ToList()
antags.ToList(),
traits.ToList()
);
}
@@ -254,6 +257,10 @@ namespace Content.Server.Database
humanoid.AntagPreferences
.Select(a => new Antag {AntagName = a})
);
entity.Traits.AddRange(
humanoid.TraitPreferences
.Select(t => new Trait {TraitName = t})
);
return entity;
}

View File

@@ -160,9 +160,6 @@ namespace Content.Server.Nutrition.Components
{
_currentHunger -= frametime * ActualDecayRate;
UpdateCurrentThreshold();
if (_currentHungerThreshold != HungerThreshold.Dead)
return;
}
private void UpdateCurrentThreshold()

View File

@@ -1,12 +1,8 @@
using Content.Server.Nutrition.Components;
using JetBrains.Annotations;
using Robust.Shared.Random;
using Content.Shared.MobState.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Alert;
using Content.Server.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Damage;
using Content.Shared.Movement.Systems;
namespace Content.Server.Nutrition.EntitySystems
@@ -43,6 +39,7 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args)
{
// TODO: This should really be taken care of somewhere else
if (_jetpack.IsUserFlying(component.Owner))
return;

View File

@@ -0,0 +1,40 @@
using Content.Server.GameTicking;
using Content.Shared.Traits;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
namespace Content.Server.Traits;
public sealed class TraitSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
}
// When the player is spawned in, add all trait components selected during character creation
private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
{
foreach (var traitId in args.Profile.TraitPreferences)
{
if (!_prototypeManager.TryIndex<TraitPrototype>(traitId, out var traitPrototype))
{
Logger.Warning($"No trait found with ID {traitId}!");
return;
}
// Add all components required by the prototype
foreach (var entry in traitPrototype.Components.Values)
{
var comp = (Component) _serializationManager.Copy(entry.Component);
comp.Owner = args.Mob;
EntityManager.AddComponent(args.Mob, comp);
}
}
}
}

View File

@@ -6,6 +6,7 @@ using Content.Shared.GameTicking;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
using Content.Shared.Species;
using Content.Shared.Traits;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
@@ -28,6 +29,7 @@ namespace Content.Shared.Preferences
private readonly Dictionary<string, JobPriority> _jobPriorities;
private readonly List<string> _antagPreferences;
private readonly List<string> _traitPreferences;
private HumanoidCharacterProfile(
string name,
@@ -41,7 +43,8 @@ namespace Content.Shared.Preferences
BackpackPreference backpack,
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List<string> antagPreferences)
List<string> antagPreferences,
List<string> traitPreferences)
{
Name = name;
FlavorText = flavortext;
@@ -55,21 +58,23 @@ namespace Content.Shared.Preferences
_jobPriorities = jobPriorities;
PreferenceUnavailable = preferenceUnavailable;
_antagPreferences = antagPreferences;
_traitPreferences = traitPreferences;
}
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
private HumanoidCharacterProfile(
HumanoidCharacterProfile other,
Dictionary<string, JobPriority> jobPriorities,
List<string> antagPreferences)
List<string> antagPreferences,
List<string> traitPreferences)
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack,
jobPriorities, other.PreferenceUnavailable, antagPreferences)
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
{
}
/// <summary>Copy constructor</summary>
private HumanoidCharacterProfile(HumanoidCharacterProfile other)
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences))
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences))
{
}
@@ -85,9 +90,10 @@ namespace Content.Shared.Preferences
BackpackPreference backpack,
IReadOnlyDictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
IReadOnlyList<string> antagPreferences)
IReadOnlyList<string> antagPreferences,
IReadOnlyList<string> traitPreferences)
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, new Dictionary<string, JobPriority>(jobPriorities),
preferenceUnavailable, new List<string>(antagPreferences))
preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences))
{
}
@@ -108,6 +114,7 @@ namespace Content.Shared.Preferences
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
new List<string>());
}
@@ -127,8 +134,8 @@ namespace Content.Shared.Preferences
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
}, PreferenceUnavailableMode.StayInLobby, new List<string>());
{SharedGameTicker.FallbackOverflowJob, JobPriority.High},
}, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>());
}
public string Name { get; private set; }
@@ -143,6 +150,7 @@ namespace Content.Shared.Preferences
public BackpackPreference Backpack { get; private set; }
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
public IReadOnlyList<string> TraitPreferences => _traitPreferences;
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
public HumanoidCharacterProfile WithName(string name)
@@ -191,7 +199,7 @@ namespace Content.Shared.Preferences
}
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
{
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences);
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences);
}
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
@@ -205,7 +213,7 @@ namespace Content.Shared.Preferences
{
dictionary[jobId] = priority;
}
return new(this, dictionary, _antagPreferences);
return new(this, dictionary, _antagPreferences, _traitPreferences);
}
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
@@ -215,7 +223,7 @@ namespace Content.Shared.Preferences
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
{
return new(this, _jobPriorities, new List<string>(antagPreferences));
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences);
}
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
@@ -235,7 +243,29 @@ namespace Content.Shared.Preferences
list.Remove(antagId);
}
}
return new(this, _jobPriorities, list);
return new(this, _jobPriorities, list, _traitPreferences);
}
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
{
var list = new List<string>(_traitPreferences);
// TODO: Maybe just refactor this to HashSet? Same with _antagPreferences
if(pref)
{
if(!list.Contains(traitId))
{
list.Add(traitId);
}
}
else
{
if(list.Contains(traitId))
{
list.Remove(traitId);
}
}
return new(this, _jobPriorities, _antagPreferences, list);
}
public string Summary =>
@@ -258,6 +288,7 @@ namespace Content.Shared.Preferences
if (Backpack != other.Backpack) return false;
if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false;
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false;
return Appearance.MemberwiseEquals(other.Appearance);
}
@@ -357,6 +388,10 @@ namespace Content.Shared.Preferences
.Where(prototypeManager.HasIndex<AntagPrototype>)
.ToList();
var traits = TraitPreferences
.Where(prototypeManager.HasIndex<TraitPrototype>)
.ToList();
Name = name;
FlavorText = flavortext;
Age = age;
@@ -377,6 +412,9 @@ namespace Content.Shared.Preferences
_antagPreferences.Clear();
_antagPreferences.AddRange(antags);
_traitPreferences.Clear();
_traitPreferences.AddRange(traits);
}
public override bool Equals(object? obj)
@@ -399,7 +437,8 @@ namespace Content.Shared.Preferences
),
PreferenceUnavailable,
_jobPriorities,
_antagPreferences
_antagPreferences,
_traitPreferences
);
}
}

View File

@@ -0,0 +1,28 @@
using Robust.Shared.Prototypes;
using static Robust.Shared.Prototypes.EntityPrototype; // don't worry about it
namespace Content.Shared.Traits
{
/// <summary>
/// Describes a trait.
/// </summary>
[Prototype("trait")]
public sealed class TraitPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; } = default!;
/// <summary>
/// The name of this trait.
/// </summary>
[DataField("name")]
public string Name { get; } = string.Empty;
/// <summary>
/// The components that get added to the player, when they pick this trait.
/// </summary>
[DataField("components")]
public ComponentRegistry Components { get; } = default!;
}
}

View File

@@ -64,6 +64,7 @@ namespace Content.Tests.Server.Preferences
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
},
PreferenceUnavailableMode.StayInLobby,
new List<string> (),
new List<string>()
);
}

View File

@@ -30,6 +30,7 @@ humanoid-profile-editor-preference-duffelbag = Duffelbag
humanoid-profile-editor-jobs-amount-in-department-tooltip = Jobs in the {$departmentName} department
humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
humanoid-profile-editor-antags-tab = Antags
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