diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 98a5af4c2ec..d7791316763 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -1195,25 +1195,31 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); modelBuilder.Entity("Content.Server.Database.Loadout", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("loadout_id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("ProfileId") - .HasColumnType("integer") - .HasColumnName("profile_id"); - b.Property("LoadoutName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("loadout_name"); - b.HasKey("Id") - .HasName("PK_loadout"); - b.HasIndex("ProfileId") - .HasDatabaseName("IX_loadout_profile_id"); - b.ToTable("loadout", (string)null); - }); + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId") + .HasDatabaseName("IX_loadout_profile_id"); + + b.ToTable("loadout", (string)null); + }); modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => { diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index c58ffda8615..5e61c63f302 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -1133,17 +1133,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER") .HasColumnName("loadout_id"); + b.Property("ProfileId") .HasColumnType("INTEGER") .HasColumnName("profile_id"); + b.Property("LoadoutName") .IsRequired() .HasColumnType("TEXT") .HasColumnName("loadout_name"); + b.HasKey("Id") .HasName("PK_loadout"); + b.HasIndex("ProfileId") .HasDatabaseName("IX_loadout_profile_id"); + b.ToTable("loadout", (string)null); }); diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index ac3563f51c0..bc91b6c1534 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -14,141 +14,112 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; -namespace Content.Shared.Preferences; - -/// -/// Character profile. Looks immutable, but uses non-immutable semantics internally for serialization/code sanity purposes. -/// -[DataDefinition] -[Serializable, NetSerializable] -public sealed partial class HumanoidCharacterProfile : ICharacterProfile +namespace Content.Shared.Preferences { - public const int MaxNameLength = 32; - public const int MaxDescLength = 512; - - private readonly Dictionary _jobPriorities; - private readonly List _antagPreferences; - private readonly List _traitPreferences; - private readonly List _loadoutPreferences; - - private HumanoidCharacterProfile( - string name, - string flavortext, - string species, - int age, - Sex sex, - Gender gender, - HumanoidCharacterAppearance appearance, - ClothingPreference clothing, - BackpackPreference backpack, - SpawnPriorityPreference spawnPriority, - Dictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable, - List antagPreferences, - List traitPreferences, - List loadoutPreferences) - { - Name = name; - FlavorText = flavortext; - Species = species; - Age = age; - Sex = sex; - Gender = gender; - Appearance = appearance; - Clothing = clothing; - Backpack = backpack; - SpawnPriority = spawnPriority; - _jobPriorities = jobPriorities; - PreferenceUnavailable = preferenceUnavailable; - _antagPreferences = antagPreferences; - _traitPreferences = traitPreferences; - _loadoutPreferences = loadoutPreferences; - } - - /// Copy constructor but with overridable references (to prevent useless copies) - private HumanoidCharacterProfile( - HumanoidCharacterProfile other, - Dictionary jobPriorities, - List antagPreferences, - List traitPreferences, - List loadoutPreferences) - : this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, - other.Clothing, other.Backpack, other.SpawnPriority, jobPriorities, other.PreferenceUnavailable, - antagPreferences, traitPreferences, loadoutPreferences) + /// + /// Character profile. Looks immutable, but uses non-immutable semantics internally for serialization/code sanity purposes. + /// + [DataDefinition] + [Serializable, NetSerializable] + public sealed partial class HumanoidCharacterProfile : ICharacterProfile { - } + public const int MaxNameLength = 32; + public const int MaxDescLength = 512; + + private readonly Dictionary _jobPriorities; + private readonly List _antagPreferences; + private readonly List _traitPreferences; + private readonly List _loadoutPreferences; + + private HumanoidCharacterProfile( + string name, + string flavortext, + string species, + int age, + Sex sex, + Gender gender, + HumanoidCharacterAppearance appearance, + ClothingPreference clothing, + BackpackPreference backpack, + SpawnPriorityPreference spawnPriority, + Dictionary jobPriorities, + PreferenceUnavailableMode preferenceUnavailable, + List antagPreferences, + List traitPreferences, + List loadoutPreferences) + { + Name = name; + FlavorText = flavortext; + Species = species; + Age = age; + Sex = sex; + Gender = gender; + Appearance = appearance; + Clothing = clothing; + Backpack = backpack; + SpawnPriority = spawnPriority; + _jobPriorities = jobPriorities; + PreferenceUnavailable = preferenceUnavailable; + _antagPreferences = antagPreferences; + _traitPreferences = traitPreferences; + _loadoutPreferences = loadoutPreferences; + } - /// Copy constructor - private HumanoidCharacterProfile(HumanoidCharacterProfile other) - : this(other, new Dictionary(other.JobPriorities), - [..other.AntagPreferences], [..other.TraitPreferences], []) - { - } + /// Copy constructor but with overridable references (to prevent useless copies) + private HumanoidCharacterProfile( + HumanoidCharacterProfile other, + Dictionary jobPriorities, + List antagPreferences, + List traitPreferences, + List loadoutPreferences) + : this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, + other.Clothing, other.Backpack, other.SpawnPriority, jobPriorities, other.PreferenceUnavailable, + antagPreferences, traitPreferences, loadoutPreferences) + { + } - public HumanoidCharacterProfile( - string name, - string flavortext, - string species, - int age, - Sex sex, - Gender gender, - HumanoidCharacterAppearance appearance, - ClothingPreference clothing, - BackpackPreference backpack, - SpawnPriorityPreference spawnPriority, - IReadOnlyDictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable, - IReadOnlyList antagPreferences, - IReadOnlyList traitPreferences, - IReadOnlyList loadoutPreferences) - : this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, spawnPriority, - new Dictionary(jobPriorities), preferenceUnavailable, - [..antagPreferences], [..traitPreferences], []) - { - } + /// Copy constructor + private HumanoidCharacterProfile(HumanoidCharacterProfile other) + : this(other, new Dictionary(other.JobPriorities), + [..other.AntagPreferences], [..other.TraitPreferences], []) + { + } - /// - /// Get the default humanoid character profile, using internal constant values. - /// Defaults to for the species. - /// - /// - public HumanoidCharacterProfile() : this( - "John Doe", - "", - SharedHumanoidAppearanceSystem.DefaultSpecies, - 18, - Sex.Male, - Gender.Male, - new HumanoidCharacterAppearance(), - ClothingPreference.Jumpsuit, - BackpackPreference.Backpack, - SpawnPriorityPreference.None, - new Dictionary + public HumanoidCharacterProfile( + string name, + string flavortext, + string species, + int age, + Sex sex, + Gender gender, + HumanoidCharacterAppearance appearance, + ClothingPreference clothing, + BackpackPreference backpack, + SpawnPriorityPreference spawnPriority, + IReadOnlyDictionary jobPriorities, + PreferenceUnavailableMode preferenceUnavailable, + IReadOnlyList antagPreferences, + IReadOnlyList traitPreferences, + IReadOnlyList loadoutPreferences) + : this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, spawnPriority, + new Dictionary(jobPriorities), preferenceUnavailable, + [..antagPreferences], [..traitPreferences], []) { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High} - }, - PreferenceUnavailableMode.SpawnAsOverflow, - [], - [], - []) - { - } + } - /// - /// Return a default character profile, based on species. - /// - /// The species to use in this default profile. The default species is . - /// Humanoid character profile with default settings. - public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) - { - return new( + /// + /// Get the default humanoid character profile, using internal constant values. + /// Defaults to for the species. + /// + /// + public HumanoidCharacterProfile() : this( "John Doe", "", - species, + SharedHumanoidAppearanceSystem.DefaultSpecies, 18, Sex.Male, Gender.Male, - HumanoidCharacterAppearance.DefaultWithSpecies(species), + new HumanoidCharacterAppearance(), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, SpawnPriorityPreference.None, @@ -159,475 +130,505 @@ public static HumanoidCharacterProfile DefaultWithSpecies(string species = Share PreferenceUnavailableMode.SpawnAsOverflow, [], [], - []); - } - - // TODO: This should eventually not be a visual change only. - public static HumanoidCharacterProfile Random(HashSet? ignoredSpecies = null) - { - var prototypeManager = IoCManager.Resolve(); - var random = IoCManager.Resolve(); - - var species = random.Pick(prototypeManager - .EnumeratePrototypes() - .Where(x => ignoredSpecies == null ? x.RoundStart : x.RoundStart && !ignoredSpecies.Contains(x.ID)) - .ToArray() - ).ID; - - return RandomWithSpecies(species); - } - - public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) - { - var prototypeManager = IoCManager.Resolve(); - var random = IoCManager.Resolve(); - - var sex = Sex.Unsexed; - var age = 18; - if (prototypeManager.TryIndex(species, out var speciesPrototype)) + []) { - sex = random.Pick(speciesPrototype.Sexes); - age = random.Next(speciesPrototype.MinAge, speciesPrototype.OldAge); // people don't look and keep making 119 year old characters with zero rp, cap it at middle aged } - var gender = Gender.Epicene; + /// + /// Return a default character profile, based on species. + /// + /// The species to use in this default profile. The default species is . + /// Humanoid character profile with default settings. + public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) + { + return new( + "John Doe", + "", + species, + 18, + Sex.Male, + Gender.Male, + HumanoidCharacterAppearance.DefaultWithSpecies(species), + ClothingPreference.Jumpsuit, + BackpackPreference.Backpack, + SpawnPriorityPreference.None, + new Dictionary + { + {SharedGameTicker.FallbackOverflowJob, JobPriority.High} + }, + PreferenceUnavailableMode.SpawnAsOverflow, + [], + [], + []); + } - switch (sex) + // TODO: This should eventually not be a visual change only. + public static HumanoidCharacterProfile Random(HashSet? ignoredSpecies = null) { - case Sex.Male: - gender = Gender.Male; - break; - case Sex.Female: - gender = Gender.Female; - break; + var prototypeManager = IoCManager.Resolve(); + var random = IoCManager.Resolve(); + + var species = random.Pick(prototypeManager + .EnumeratePrototypes() + .Where(x => ignoredSpecies == null ? x.RoundStart : x.RoundStart && !ignoredSpecies.Contains(x.ID)) + .ToArray() + ).ID; + + return RandomWithSpecies(species); } - var name = GetName(species, gender); + public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) + { + var prototypeManager = IoCManager.Resolve(); + var random = IoCManager.Resolve(); - return new HumanoidCharacterProfile(name, "", species, age, sex, gender, - HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, - BackpackPreference.Backpack, SpawnPriorityPreference.None, - new Dictionary + var sex = Sex.Unsexed; + var age = 18; + if (prototypeManager.TryIndex(species, out var speciesPrototype)) { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High}, - }, PreferenceUnavailableMode.StayInLobby, [], [], []); - } - - public string Name { get; private set; } - public string FlavorText { get; private set; } - public string Species { get; private set; } + sex = random.Pick(speciesPrototype.Sexes); + age = random.Next(speciesPrototype.MinAge, speciesPrototype.OldAge); // people don't look and keep making 119 year old characters with zero rp, cap it at middle aged + } - [DataField("age")] - public int Age { get; private set; } + var gender = Gender.Epicene; - [DataField("sex")] - public Sex Sex { get; private set; } + switch (sex) + { + case Sex.Male: + gender = Gender.Male; + break; + case Sex.Female: + gender = Gender.Female; + break; + } - [DataField("gender")] - public Gender Gender { get; private set; } + var name = GetName(species, gender); - public ICharacterAppearance CharacterAppearance => Appearance; + return new HumanoidCharacterProfile(name, "", species, age, sex, gender, + HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, + BackpackPreference.Backpack, SpawnPriorityPreference.None, + new Dictionary + { + {SharedGameTicker.FallbackOverflowJob, JobPriority.High}, + }, PreferenceUnavailableMode.StayInLobby, [], [], []); + } - [DataField("appearance")] - public HumanoidCharacterAppearance Appearance { get; private set; } - public ClothingPreference Clothing { get; private set; } - public BackpackPreference Backpack { get; private set; } - public SpawnPriorityPreference SpawnPriority { get; private set; } - public IReadOnlyDictionary JobPriorities => _jobPriorities; - public IReadOnlyList AntagPreferences => _antagPreferences; - public IReadOnlyList TraitPreferences => _traitPreferences; - public IReadOnlyList LoadoutPreferences => _loadoutPreferences; - public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } + public string Name { get; private set; } + public string FlavorText { get; private set; } + public string Species { get; private set; } - public HumanoidCharacterProfile WithName(string name) - { - return new(this) { Name = name }; - } + [DataField("age")] + public int Age { get; private set; } - public HumanoidCharacterProfile WithFlavorText(string flavorText) - { - return new(this) { FlavorText = flavorText }; - } + [DataField("sex")] + public Sex Sex { get; private set; } - public HumanoidCharacterProfile WithAge(int age) - { - return new(this) { Age = age }; - } + [DataField("gender")] + public Gender Gender { get; private set; } - public HumanoidCharacterProfile WithSex(Sex sex) - { - return new(this) { Sex = sex }; - } + public ICharacterAppearance CharacterAppearance => Appearance; - public HumanoidCharacterProfile WithGender(Gender gender) - { - return new(this) { Gender = gender }; - } + [DataField("appearance")] + public HumanoidCharacterAppearance Appearance { get; private set; } + public ClothingPreference Clothing { get; private set; } + public BackpackPreference Backpack { get; private set; } + public SpawnPriorityPreference SpawnPriority { get; private set; } + public IReadOnlyDictionary JobPriorities => _jobPriorities; + public IReadOnlyList AntagPreferences => _antagPreferences; + public IReadOnlyList TraitPreferences => _traitPreferences; + public IReadOnlyList LoadoutPreferences => _loadoutPreferences; + public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } - public HumanoidCharacterProfile WithSpecies(string species) - { - return new(this) { Species = species }; - } + public HumanoidCharacterProfile WithName(string name) + { + return new(this) { Name = name }; + } + public HumanoidCharacterProfile WithFlavorText(string flavorText) + { + return new(this) { FlavorText = flavorText }; + } - public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) - { - return new(this) { Appearance = appearance }; - } + public HumanoidCharacterProfile WithAge(int age) + { + return new(this) { Age = age }; + } - public HumanoidCharacterProfile WithClothingPreference(ClothingPreference clothing) - { - return new(this) { Clothing = clothing }; - } - public HumanoidCharacterProfile WithBackpackPreference(BackpackPreference backpack) - { - return new(this) { Backpack = backpack }; - } - public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority) - { - return new(this) { SpawnPriority = spawnPriority }; - } - public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) - { - return new(this, new Dictionary(jobPriorities), _antagPreferences, _traitPreferences, - _loadoutPreferences); - } + public HumanoidCharacterProfile WithSex(Sex sex) + { + return new(this) { Sex = sex }; + } - public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) - { - var dictionary = new Dictionary(_jobPriorities); - if (priority == JobPriority.Never) + public HumanoidCharacterProfile WithGender(Gender gender) { - dictionary.Remove(jobId); + return new(this) { Gender = gender }; } - else + + public HumanoidCharacterProfile WithSpecies(string species) { - dictionary[jobId] = priority; + return new(this) { Species = species }; } - return new(this, dictionary, _antagPreferences, _traitPreferences, _loadoutPreferences); - } - public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode) - { - return new(this) { PreferenceUnavailable = mode }; - } - public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) - { - return new(this, _jobPriorities, new List(antagPreferences), _traitPreferences, - _loadoutPreferences); - } + public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) + { + return new(this) { Appearance = appearance }; + } - public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) - { - var list = new List(_antagPreferences); - if (pref) + public HumanoidCharacterProfile WithClothingPreference(ClothingPreference clothing) { - if (!list.Contains(antagId)) - { - list.Add(antagId); - } + return new(this) { Clothing = clothing }; } - else + public HumanoidCharacterProfile WithBackpackPreference(BackpackPreference backpack) { - if (list.Contains(antagId)) - { - list.Remove(antagId); - } + return new(this) { Backpack = backpack }; } - return new(this, _jobPriorities, list, _traitPreferences, _loadoutPreferences); - } - - public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) - { - var list = new List(_traitPreferences); - - // TODO: Maybe just refactor this to HashSet? Same with _antagPreferences - if (pref) + public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority) { - if (!list.Contains(traitId)) - { - list.Add(traitId); - } + return new(this) { SpawnPriority = spawnPriority }; } - else + public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) { - if (list.Contains(traitId)) - { - list.Remove(traitId); - } + return new(this, new Dictionary(jobPriorities), _antagPreferences, _traitPreferences, + _loadoutPreferences); } - return new(this, _jobPriorities, _antagPreferences, list, _loadoutPreferences); - } - public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pref) - { - var list = new List(_loadoutPreferences); - - if(pref) + public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) { - if(!list.Contains(loadoutId)) + var dictionary = new Dictionary(_jobPriorities); + if (priority == JobPriority.Never) { - list.Add(loadoutId); + dictionary.Remove(jobId); } - } - else - { - if(list.Contains(loadoutId)) + else { - list.Remove(loadoutId); + dictionary[jobId] = priority; } + return new(this, dictionary, _antagPreferences, _traitPreferences, _loadoutPreferences); } - return new(this, _jobPriorities, _antagPreferences, _traitPreferences, list); - } - - public string Summary => - Loc.GetString( - "humanoid-character-profile-summary", - ("name", Name), - ("gender", Gender.ToString().ToLowerInvariant()), - ("age", Age) - ); - public bool MemberwiseEquals(ICharacterProfile maybeOther) - { - if (maybeOther is not HumanoidCharacterProfile other || - Name != other.Name || - Age != other.Age || - Sex != other.Sex || - Gender != other.Gender || - PreferenceUnavailable != other.PreferenceUnavailable || - Clothing != other.Clothing || - Backpack != other.Backpack || - SpawnPriority != other.SpawnPriority || - !_jobPriorities.SequenceEqual(other._jobPriorities) || - !_antagPreferences.SequenceEqual(other._antagPreferences) || - !_traitPreferences.SequenceEqual(other._traitPreferences) || - !_loadoutPreferences.SequenceEqual(other._loadoutPreferences)) - return false; - - return Appearance.MemberwiseEquals(other.Appearance); - } - - public void EnsureValid(IConfigurationManager configManager, IPrototypeManager prototypeManager) - { - if (!prototypeManager.TryIndex(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false) + public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode) { - Species = SharedHumanoidAppearanceSystem.DefaultSpecies; - speciesPrototype = prototypeManager.Index(Species); + return new(this) { PreferenceUnavailable = mode }; } - var sex = Sex switch + public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) { - Sex.Male => Sex.Male, - Sex.Female => Sex.Female, - Sex.Unsexed => Sex.Unsexed, - _ => Sex.Male // Invalid enum values. - }; - - // ensure the species can be that sex and their age fits the founds - if (!speciesPrototype.Sexes.Contains(sex)) - sex = speciesPrototype.Sexes[0]; - - var age = Math.Clamp(Age, speciesPrototype.MinAge, speciesPrototype.MaxAge); + return new(this, _jobPriorities, new List(antagPreferences), _traitPreferences, + _loadoutPreferences); + } - var gender = Gender switch - { - Gender.Epicene => Gender.Epicene, - Gender.Female => Gender.Female, - Gender.Male => Gender.Male, - Gender.Neuter => Gender.Neuter, - _ => Gender.Epicene // Invalid enum values. - }; - - string name; - if (string.IsNullOrEmpty(Name)) + public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) { - name = GetName(Species, gender); + var list = new List(_antagPreferences); + if (pref) + { + if (!list.Contains(antagId)) + { + list.Add(antagId); + } + } + else + { + if (list.Contains(antagId)) + { + list.Remove(antagId); + } + } + return new(this, _jobPriorities, list, _traitPreferences, _loadoutPreferences); } - else if (Name.Length > MaxNameLength) + + public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) { - name = Name[..MaxNameLength]; + var list = new List(_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, _loadoutPreferences); } - else + + public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pref) { - name = Name; + var list = new List(_loadoutPreferences); + + if(pref) + { + if(!list.Contains(loadoutId)) + { + list.Add(loadoutId); + } + } + else + { + if(list.Contains(loadoutId)) + { + list.Remove(loadoutId); + } + } + return new(this, _jobPriorities, _antagPreferences, _traitPreferences, list); } - name = name.Trim(); + public string Summary => + Loc.GetString( + "humanoid-character-profile-summary", + ("name", Name), + ("gender", Gender.ToString().ToLowerInvariant()), + ("age", Age) + ); - if (configManager.GetCVar(CCVars.RestrictedNames)) + public bool MemberwiseEquals(ICharacterProfile maybeOther) { - name = Regex.Replace(name, @"[^\u0030-\u0039,\u0041-\u005A,\u0061-\u007A,\u00C0-\u00D6,\u00D8-\u00F6,\u00F8-\u00FF,\u0100-\u017F, '.,-]", string.Empty); - /* - * 0030-0039 Basic Latin: ASCII Digits - * 0041-005A Basic Latin: Uppercase Latin Alphabet - * 0061-007A Basic Latin: Lowercase Latin Alphabet - * 00C0-00D6 Latin-1 Supplement: Letters I - * 00D8-00F6 Latin-1 Supplement: Letters II - * 00F8-00FF Latin-1 Supplement: Letters III - * 0100-017F Latin Extended A: European Latin - */ + if (maybeOther is not HumanoidCharacterProfile other || + Name != other.Name || + Age != other.Age || + Sex != other.Sex || + Gender != other.Gender || + PreferenceUnavailable != other.PreferenceUnavailable || + Clothing != other.Clothing || + Backpack != other.Backpack || + SpawnPriority != other.SpawnPriority || + !_jobPriorities.SequenceEqual(other._jobPriorities) || + !_antagPreferences.SequenceEqual(other._antagPreferences) || + !_traitPreferences.SequenceEqual(other._traitPreferences) || + !_loadoutPreferences.SequenceEqual(other._loadoutPreferences)) + return false; + + return Appearance.MemberwiseEquals(other.Appearance); } - if (configManager.GetCVar(CCVars.ICNameCase)) + public void EnsureValid(IConfigurationManager configManager, IPrototypeManager prototypeManager) { - // This regex replaces the first character of the first and last words of the name with their uppercase version - name = Regex.Replace(name, - @"^(?\w)|\b(?\w)(?=\w*$)", - m => m.Groups["word"].Value.ToUpper()); - } + if (!prototypeManager.TryIndex(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false) + { + Species = SharedHumanoidAppearanceSystem.DefaultSpecies; + speciesPrototype = prototypeManager.Index(Species); + } - if (string.IsNullOrEmpty(name)) - { - name = GetName(Species, gender); - } + var sex = Sex switch + { + Sex.Male => Sex.Male, + Sex.Female => Sex.Female, + Sex.Unsexed => Sex.Unsexed, + _ => Sex.Male // Invalid enum values. + }; - string flavortext; - if (FlavorText.Length > MaxDescLength) - { - flavortext = FormattedMessage.RemoveMarkup(FlavorText)[..MaxDescLength]; - } - else - { - flavortext = FormattedMessage.RemoveMarkup(FlavorText); - } + // ensure the species can be that sex and their age fits the founds + if (!speciesPrototype.Sexes.Contains(sex)) + sex = speciesPrototype.Sexes[0]; - var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex); + var age = Math.Clamp(Age, speciesPrototype.MinAge, speciesPrototype.MaxAge); - var prefsUnavailableMode = PreferenceUnavailable switch - { - PreferenceUnavailableMode.StayInLobby => PreferenceUnavailableMode.StayInLobby, - PreferenceUnavailableMode.SpawnAsOverflow => PreferenceUnavailableMode.SpawnAsOverflow, - _ => PreferenceUnavailableMode.StayInLobby // Invalid enum values. - }; + var gender = Gender switch + { + Gender.Epicene => Gender.Epicene, + Gender.Female => Gender.Female, + Gender.Male => Gender.Male, + Gender.Neuter => Gender.Neuter, + _ => Gender.Epicene // Invalid enum values. + }; + + string name; + if (string.IsNullOrEmpty(Name)) + { + name = GetName(Species, gender); + } + else if (Name.Length > MaxNameLength) + { + name = Name[..MaxNameLength]; + } + else + { + name = Name; + } - var clothing = Clothing switch - { - ClothingPreference.Jumpsuit => ClothingPreference.Jumpsuit, - ClothingPreference.Jumpskirt => ClothingPreference.Jumpskirt, - _ => ClothingPreference.Jumpsuit // Invalid enum values. - }; + name = name.Trim(); - var backpack = Backpack switch - { - BackpackPreference.Backpack => BackpackPreference.Backpack, - BackpackPreference.Satchel => BackpackPreference.Satchel, - BackpackPreference.Duffelbag => BackpackPreference.Duffelbag, - _ => BackpackPreference.Backpack // Invalid enum values. - }; + if (configManager.GetCVar(CCVars.RestrictedNames)) + { + name = Regex.Replace(name, @"[^\u0030-\u0039,\u0041-\u005A,\u0061-\u007A,\u00C0-\u00D6,\u00D8-\u00F6,\u00F8-\u00FF,\u0100-\u017F, '.,-]", string.Empty); + /* + * 0030-0039 Basic Latin: ASCII Digits + * 0041-005A Basic Latin: Uppercase Latin Alphabet + * 0061-007A Basic Latin: Lowercase Latin Alphabet + * 00C0-00D6 Latin-1 Supplement: Letters I + * 00D8-00F6 Latin-1 Supplement: Letters II + * 00F8-00FF Latin-1 Supplement: Letters III + * 0100-017F Latin Extended A: European Latin + */ + } - var spawnPriority = SpawnPriority switch - { - SpawnPriorityPreference.None => SpawnPriorityPreference.None, - SpawnPriorityPreference.Arrivals => SpawnPriorityPreference.Arrivals, - SpawnPriorityPreference.Cryosleep => SpawnPriorityPreference.Cryosleep, - _ => SpawnPriorityPreference.None // Invalid enum values. - }; - - var priorities = new Dictionary(JobPriorities - .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch + if (configManager.GetCVar(CCVars.ICNameCase)) { - JobPriority.Never => false, // Drop never since that's assumed default. - JobPriority.Low => true, - JobPriority.Medium => true, - JobPriority.High => true, - _ => false - })); + // This regex replaces the first character of the first and last words of the name with their uppercase version + name = Regex.Replace(name, + @"^(?\w)|\b(?\w)(?=\w*$)", + m => m.Groups["word"].Value.ToUpper()); + } - var antags = AntagPreferences - .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) - .ToList(); + if (string.IsNullOrEmpty(name)) + { + name = GetName(Species, gender); + } - var traits = TraitPreferences - .Where(prototypeManager.HasIndex) - .ToList(); + string flavortext; + if (FlavorText.Length > MaxDescLength) + { + flavortext = FormattedMessage.RemoveMarkup(FlavorText)[..MaxDescLength]; + } + else + { + flavortext = FormattedMessage.RemoveMarkup(FlavorText); + } - var loadouts = LoadoutPreferences - .Where(prototypeManager.HasIndex) - .ToList(); + var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex); - var maxLoadouts = configManager.GetCVar(CCVars.GameLoadoutsPoints); - var currentLoadouts = 0; + var prefsUnavailableMode = PreferenceUnavailable switch + { + PreferenceUnavailableMode.StayInLobby => PreferenceUnavailableMode.StayInLobby, + PreferenceUnavailableMode.SpawnAsOverflow => PreferenceUnavailableMode.SpawnAsOverflow, + _ => PreferenceUnavailableMode.StayInLobby // Invalid enum values. + }; - foreach (var loadout in loadouts.ToList()) - { - var proto = prototypeManager.Index(loadout); + var clothing = Clothing switch + { + ClothingPreference.Jumpsuit => ClothingPreference.Jumpsuit, + ClothingPreference.Jumpskirt => ClothingPreference.Jumpskirt, + _ => ClothingPreference.Jumpsuit // Invalid enum values. + }; - if (currentLoadouts + proto.Cost > maxLoadouts) - loadouts.Remove(loadout); - else - currentLoadouts += proto.Cost; - } + var backpack = Backpack switch + { + BackpackPreference.Backpack => BackpackPreference.Backpack, + BackpackPreference.Satchel => BackpackPreference.Satchel, + BackpackPreference.Duffelbag => BackpackPreference.Duffelbag, + _ => BackpackPreference.Backpack // Invalid enum values. + }; - Name = name; - FlavorText = flavortext; - Age = age; - Sex = sex; - Gender = gender; - Appearance = appearance; - Clothing = clothing; - Backpack = backpack; - SpawnPriority = spawnPriority; + var spawnPriority = SpawnPriority switch + { + SpawnPriorityPreference.None => SpawnPriorityPreference.None, + SpawnPriorityPreference.Arrivals => SpawnPriorityPreference.Arrivals, + SpawnPriorityPreference.Cryosleep => SpawnPriorityPreference.Cryosleep, + _ => SpawnPriorityPreference.None // Invalid enum values. + }; + + var priorities = new Dictionary(JobPriorities + .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch + { + JobPriority.Never => false, // Drop never since that's assumed default. + JobPriority.Low => true, + JobPriority.Medium => true, + JobPriority.High => true, + _ => false + })); + + var antags = AntagPreferences + .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) + .ToList(); + + var traits = TraitPreferences + .Where(prototypeManager.HasIndex) + .ToList(); + + var loadouts = LoadoutPreferences + .Where(prototypeManager.HasIndex) + .ToList(); + + var maxLoadouts = configManager.GetCVar(CCVars.GameLoadoutsPoints); + var currentLoadouts = 0; + + foreach (var loadout in loadouts.ToList()) + { + var proto = prototypeManager.Index(loadout); - _jobPriorities.Clear(); + if (currentLoadouts + proto.Cost > maxLoadouts) + loadouts.Remove(loadout); + else + currentLoadouts += proto.Cost; + } - foreach (var (job, priority) in priorities) - { - _jobPriorities.Add(job, priority); - } + Name = name; + FlavorText = flavortext; + Age = age; + Sex = sex; + Gender = gender; + Appearance = appearance; + Clothing = clothing; + Backpack = backpack; + SpawnPriority = spawnPriority; - PreferenceUnavailable = prefsUnavailableMode; + _jobPriorities.Clear(); - _antagPreferences.Clear(); - _antagPreferences.AddRange(antags); + foreach (var (job, priority) in priorities) + { + _jobPriorities.Add(job, priority); + } - _traitPreferences.Clear(); - _traitPreferences.AddRange(traits); + PreferenceUnavailable = prefsUnavailableMode; - _loadoutPreferences.Clear(); - _loadoutPreferences.AddRange(loadouts); - } + _antagPreferences.Clear(); + _antagPreferences.AddRange(antags); - public ICharacterProfile Validated(IConfigurationManager configManager, IPrototypeManager prototypeManager) - { - var profile = new HumanoidCharacterProfile(this); - profile.EnsureValid(configManager, prototypeManager); - return profile; - } + _traitPreferences.Clear(); + _traitPreferences.AddRange(traits); - // sorry this is kind of weird and duplicated, - /// working inside these non entity systems is a bit wack - public static string GetName(string species, Gender gender) - { - var namingSystem = IoCManager.Resolve().GetEntitySystem(); - return namingSystem.GetName(species, gender); - } + _loadoutPreferences.Clear(); + _loadoutPreferences.AddRange(loadouts); + } - public override bool Equals(object? obj) - { - return obj is HumanoidCharacterProfile other && MemberwiseEquals(other); - } + public ICharacterProfile Validated(IConfigurationManager configManager, IPrototypeManager prototypeManager) + { + var profile = new HumanoidCharacterProfile(this); + profile.EnsureValid(configManager, prototypeManager); + return profile; + } - public override int GetHashCode() - { - return HashCode.Combine( - HashCode.Combine( - Name, - Species, - Age, - Sex, - Gender, - Appearance, - Clothing, - Backpack - ), - SpawnPriority, - PreferenceUnavailable, - _jobPriorities, - _antagPreferences, - _traitPreferences, - _loadoutPreferences - ); + // sorry this is kind of weird and duplicated, + /// working inside these non entity systems is a bit wack + public static string GetName(string species, Gender gender) + { + var namingSystem = IoCManager.Resolve().GetEntitySystem(); + return namingSystem.GetName(species, gender); + } + + public override bool Equals(object? obj) + { + return obj is HumanoidCharacterProfile other && MemberwiseEquals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine( + HashCode.Combine( + Name, + Species, + Age, + Sex, + Gender, + Appearance, + Clothing, + Backpack + ), + SpawnPriority, + PreferenceUnavailable, + _jobPriorities, + _antagPreferences, + _traitPreferences, + _loadoutPreferences + ); + } } }