diff --git a/Content.Client/CharacterAppearance/Systems/HumanoidAppearanceSystem.cs b/Content.Client/CharacterAppearance/Systems/HumanoidAppearanceSystem.cs index b12a2339ba..ba9aaf098d 100644 --- a/Content.Client/CharacterAppearance/Systems/HumanoidAppearanceSystem.cs +++ b/Content.Client/CharacterAppearance/Systems/HumanoidAppearanceSystem.cs @@ -41,9 +41,10 @@ namespace Content.Client.CharacterAppearance.Systems HumanoidVisualLayers.LFoot }; - private void UpdateLooks(EntityUid uid, HumanoidAppearanceComponent component, ChangedHumanoidAppearanceEvent args) + private void UpdateLooks(EntityUid uid, HumanoidAppearanceComponent component, + ChangedHumanoidAppearanceEvent args) { - if(!EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) + if (!EntityManager.TryGetComponent(uid, out SpriteComponent? sprite)) return; if (EntityManager.TryGetComponent(uid, out SharedBodyComponent? body)) @@ -58,13 +59,18 @@ namespace Content.Client.CharacterAppearance.Systems } } - sprite.LayerSetColor(HumanoidVisualLayers.Hair, - component.CanColorHair ? component.Appearance.HairColor : Color.White); - sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, - component.CanColorFacialHair ? component.Appearance.FacialHairColor : Color.White); + var hairColor = component.CanColorHair ? component.Appearance.HairColor : Color.White; + hairColor = component.HairMatchesSkin ? component.Appearance.SkinColor : hairColor; + sprite.LayerSetColor(HumanoidVisualLayers.Hair, hairColor.WithAlpha(component.HairAlpha)); + + var facialHairColor = component.CanColorHair ? component.Appearance.FacialHairColor : Color.White; + facialHairColor = component.HairMatchesSkin ? component.Appearance.SkinColor : facialHairColor; + sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, facialHairColor.WithAlpha(component.HairAlpha)); foreach (var layer in _bodyPartLayers) + { sprite.LayerSetColor(layer, component.Appearance.SkinColor); + } sprite.LayerSetColor(HumanoidVisualLayers.Eyes, component.Appearance.EyeColor); diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs index 074e3e90b3..e8465741ff 100644 --- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs +++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs @@ -5,6 +5,7 @@ using Content.Client.Resources; using Content.Shared.CharacterAppearance.Systems; using Content.Shared.Preferences; using Content.Shared.Roles; +using Content.Shared.Species; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -27,6 +28,7 @@ namespace Content.Client.Preferences.UI { private readonly IClientPreferencesManager _preferencesManager; private readonly IEntityManager _entityManager; + private readonly IPrototypeManager _prototypeManager; private readonly Button _createNewCharacterButton; private readonly HumanoidProfileEditor _humanoidProfileEditor; @@ -38,6 +40,7 @@ namespace Content.Client.Preferences.UI { RobustXamlLoader.Load(this); _entityManager = entityManager; + _prototypeManager = prototypeManager; _preferencesManager = preferencesManager; var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); @@ -113,6 +116,7 @@ namespace Content.Client.Preferences.UI numberOfFullSlots++; var characterPickerButton = new CharacterPickerButton(_entityManager, _preferencesManager, + _prototypeManager, characterButtonsGroup, character); Characters.AddChild(characterPickerButton); @@ -141,6 +145,7 @@ namespace Content.Client.Preferences.UI public CharacterPickerButton( IEntityManager entityManager, IClientPreferencesManager preferencesManager, + IPrototypeManager prototypeManager, ButtonGroup group, ICharacterProfile profile) { @@ -148,9 +153,19 @@ namespace Content.Client.Preferences.UI ToggleMode = true; Group = group; - _previewDummy = entityManager.SpawnEntity("MobHumanDummy", MapCoordinates.Nullspace); - EntitySystem.Get().UpdateFromProfile(_previewDummy, profile); var humanoid = profile as HumanoidCharacterProfile; + if (humanoid is not null) + { + var dummy = prototypeManager.Index(humanoid.Species).DollPrototype; + _previewDummy = entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + } + else + { + _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SpeciesManager.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); + } + + EntitySystem.Get().UpdateFromProfile(_previewDummy, profile); + if (humanoid != null) { LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid); diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml index 815ada6273..1dc9b1ed6b 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml @@ -76,6 +76,15 @@ + + + + + + + diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs index 7240cc8964..4bfa4f4ddd 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs @@ -10,6 +10,7 @@ using Content.Shared.CharacterAppearance.Systems; using Content.Shared.GameTicking; using Content.Shared.Preferences; using Content.Shared.Roles; +using Content.Shared.Species; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -28,6 +29,7 @@ using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; using static Robust.Client.UserInterface.Controls.BoxContainer; +using Range = Robust.Client.UserInterface.Controls.Range; namespace Content.Client.Preferences.UI { @@ -55,6 +57,7 @@ namespace Content.Client.Preferences.UI private Button _randomizeEverythingButton => CRandomizeEverything; private RichTextLabel _warningLabel => CWarningLabel; private readonly IClientPreferencesManager _preferencesManager; + private readonly IEntityManager _entMan; private Button _saveButton => CSaveButton; private Button _sexFemaleButton => CSexFemale; private Button _sexMaleButton => CSexMale; @@ -72,14 +75,15 @@ namespace Content.Client.Preferences.UI private readonly List _jobPriorities; private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton; private readonly Dictionary _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 _speciesList; private readonly List _antagPreferences; - private readonly EntityUid _previewDummy; + private EntityUid _previewDummy; private Control _previewSpriteControl => CSpriteViewFront; private Control _previewSpriteSideControl => CSpriteViewSide; - private readonly SpriteView _previewSprite; - private readonly SpriteView _previewSpriteSide; + private SpriteView? _previewSprite; + private SpriteView? _previewSpriteSide; private bool _isDirty; private bool _needUpdatePreview; @@ -94,7 +98,7 @@ namespace Content.Client.Preferences.UI RobustXamlLoader.Load(this); _random = IoCManager.Resolve(); _prototypeManager = prototypeManager; - + _entMan = entityManager; _preferencesManager = preferencesManager; #region Left @@ -171,6 +175,23 @@ namespace Content.Client.Preferences.UI #endregion Gender + #region Species + + _speciesList = prototypeManager.EnumeratePrototypes().ToList(); + for (var i = 0; i < _speciesList.Count; i++) + { + CSpeciesButton.AddItem(_speciesList[i].Name, i); + } + + CSpeciesButton.OnItemSelected += args => + { + CSpeciesButton.SelectId(args.Id); + SetSpecies(_speciesList[args.Id].ID); + OnSkinColorOnValueChanged(CSkin); + }; + + #endregion Species + #region Skin // 0 - 100, 0 being gold/yellowish and 100 being dark @@ -181,33 +202,7 @@ namespace Content.Client.Preferences.UI // 0 is 45 - 20 - 100 // 20 is 25 - 20 - 100 // 100 is 25 - 100 - 20 - _skinColor.OnValueChanged += range => - { - if (Profile is null) - return; - - int rangeOffset = (int) range.Value - 20; - - float hue = 25; - float sat = 20; - float val = 100; - - if (rangeOffset < 0) - { - hue += Math.Abs(rangeOffset); - } - else if (rangeOffset > 0) - { - sat += rangeOffset; - val -= rangeOffset; - } - - var color = Color.FromHsv(new Vector4(hue / 360, sat / 100, val / 100, 1.0f)); - - Profile = Profile.WithCharacterAppearance( - Profile.Appearance.WithSkinColor(color)); - IsDirty = true; - }; + _skinColor.OnValueChanged += OnSkinColorOnValueChanged; #endregion @@ -434,12 +429,76 @@ namespace Content.Client.Preferences.UI #endregion Left - #region Right + if (preferencesManager.ServerDataLoaded) + { + LoadServerData(); + } - #region Preview + preferencesManager.OnServerDataLoaded += LoadServerData; - _previewDummy = entityManager.SpawnEntity("MobHumanDummy", MapCoordinates.Nullspace); - var sprite = IoCManager.Resolve().GetComponent(_previewDummy); + IsDirty = false; + } + + private void OnSkinColorOnValueChanged(Range range) + { + if (Profile is null) return; + + var skin = _prototypeManager.Index(Profile.Species).SkinColoration; + + switch (skin) + { + case SpeciesSkinColor.HumanToned: + { + var rangeOffset = (int) range.Value - 20; + + float hue = 25; + float sat = 20; + float val = 100; + + if (rangeOffset <= 0) + { + hue += Math.Abs(rangeOffset); + } + else + { + sat += rangeOffset; + val -= rangeOffset; + } + + var color = Color.FromHsv(new Vector4(hue / 360, sat / 100, val / 100, 1.0f)); + + Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color)); + break; + } + case SpeciesSkinColor.Hues: + { + var color = Color.FromHsv(new Vector4(range.Value / 100.0f, 1.0f, 1.0f, 1.0f)); + Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color)); + break; + } + } + + IsDirty = true; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _entMan.DeleteEntity(_previewDummy); + _preferencesManager.OnServerDataLoaded -= LoadServerData; + } + + private void RebuildSpriteView() + { + var dollProto = _prototypeManager.Index(Profile?.Species ?? SpeciesManager.DefaultSpecies).DollPrototype; + _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace); + + var sprite = _entMan.GetComponent(_previewDummy); + + _previewSpriteControl.DisposeAllChildren(); // Front _previewSprite = new SpriteView @@ -452,6 +511,8 @@ namespace Content.Client.Preferences.UI }; _previewSpriteControl.AddChild(_previewSprite); + _previewSpriteSideControl.DisposeAllChildren(); + // Side _previewSpriteSide = new SpriteView { @@ -462,29 +523,6 @@ namespace Content.Client.Preferences.UI SizeFlagsStretchRatio = 1 }; _previewSpriteSideControl.AddChild(_previewSpriteSide); - - #endregion Right - - #endregion - - if (preferencesManager.ServerDataLoaded) - { - LoadServerData(); - } - - preferencesManager.OnServerDataLoaded += LoadServerData; - - IsDirty = false; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - return; - - IoCManager.Resolve().DeleteEntity((EntityUid) _previewDummy); - _preferencesManager.OnServerDataLoaded -= LoadServerData; } private void LoadServerData() @@ -512,6 +550,13 @@ namespace Content.Client.Preferences.UI IsDirty = true; } + private void SetSpecies(string newSpecies) + { + Profile = Profile?.WithSpecies(newSpecies); + OnSkinColorOnValueChanged(CSkin); // Species may have special color prefs, make sure to update it. + IsDirty = true; + } + private void SetName(string newName) { Profile = Profile?.WithName(newName); @@ -576,20 +621,45 @@ namespace Content.Client.Preferences.UI if (Profile == null) return; + var skin = _prototypeManager.Index(Profile.Species).SkinColoration; var color = Color.ToHsv(Profile.Appearance.SkinColor); - // check for hue/value first, if hue is lower than this percentage - // and value is 1.0 - // then it'll be hue - if (Math.Clamp(color.X, 25f / 360f, 1) > 25f / 360f - && color.Z == 1.0) + + switch (skin) { - _skinColor.Value = Math.Abs(45 - (color.X * 360)); + case SpeciesSkinColor.HumanToned: + { + // check for hue/value first, if hue is lower than this percentage + // and value is 1.0 + // then it'll be hue + if (Math.Clamp(color.X, 25f / 360f, 1) > 25f / 360f + && color.Z == 1.0) + { + _skinColor.Value = Math.Abs(45 - (color.X * 360)); + } + // otherwise it'll directly be the saturation + else + { + _skinColor.Value = color.Y * 100; + } + break; + } + case SpeciesSkinColor.Hues: + { + _skinColor.Value = color.X * 100; + break; + } } - // otherwise it'll directly be the saturation - else + + } + + private void UpdateSpecies() + { + if (Profile == null) { - _skinColor.Value = color.Y * 100; + return; } + + CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species)); } private void UpdateGenderControls() @@ -660,6 +730,7 @@ namespace Content.Client.Preferences.UI { if (Profile is null) return; + RebuildSpriteView(); EntitySystem.Get().UpdateFromProfile(_previewDummy, Profile); LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, Profile); @@ -672,6 +743,7 @@ namespace Content.Client.Preferences.UI UpdateSexControls(); UpdateGenderControls(); UpdateSkinColor(); + UpdateSpecies(); UpdateClothingControls(); UpdateBackpackControls(); UpdateAgeEdit(); diff --git a/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.Designer.cs b/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.Designer.cs new file mode 100644 index 0000000000..8d75d66558 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.Designer.cs @@ -0,0 +1,870 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20220108185749_add-species")] + partial class addspecies + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id", "RoundId") + .HasName("PK_admin_log"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_log_round_id"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.Property("Uid") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uid"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Uid")); + + b.Property("AdminLogId") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("AdminLogRoundId") + .HasColumnType("integer") + .HasColumnName("admin_log_round_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Uid") + .HasName("PK_admin_log_entity"); + + b.HasIndex("AdminLogId", "AdminLogRoundId") + .HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id"); + + b.ToTable("admin_log_entity", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("PlayerUserId", "LogId", "RoundId") + .HasName("PK_admin_log_player"); + + b.HasIndex("LogId", "RoundId"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_rank_flag_admin_rank_id"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId") + .HasDatabaseName("IX_job_profile_id"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + + b.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property?>("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("UserId"); + + b.ToTable("server_ban", (string)null); + + b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.HasKey("Id") + .HasName("PK_round"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.HasOne("Content.Server.Database.AdminLog", null) + .WithMany("Entities") + .HasForeignKey("AdminLogId", "AdminLogRoundId") + .HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("LogId", "RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerUnban", b => + { + b.HasOne("Content.Server.Database.PostgresServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.PostgresServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Entities"); + + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.PostgresServerBan", b => + { + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.cs b/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.cs new file mode 100644 index 0000000000..8caac24518 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20220108185749_add-species.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class addspecies : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "species", + table: "profile", + type: "text", + nullable: false, + defaultValue: ""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "species", + table: "profile"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 10cec61b9c..0c8a3491f8 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -611,6 +611,11 @@ namespace Content.Server.Database.Migrations.Postgres .HasColumnType("integer") .HasColumnName("slot"); + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + b.HasKey("Id") .HasName("PK_profile"); diff --git a/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.Designer.cs new file mode 100644 index 0000000000..d46eb5ac30 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.Designer.cs @@ -0,0 +1,816 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20220108185734_add-species")] + partial class addspecies + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("Id", "RoundId") + .HasName("PK_admin_log"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_log_round_id"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.Property("Uid") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uid"); + + b.Property("AdminLogId") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("AdminLogRoundId") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_round_id"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Uid") + .HasName("PK_admin_log_entity"); + + b.HasIndex("AdminLogId", "AdminLogRoundId") + .HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id"); + + b.ToTable("admin_log_entity", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("PlayerUserId", "LogId", "RoundId") + .HasName("PK_admin_log_player"); + + b.HasIndex("LogId", "RoundId"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_rank_flag_admin_rank_id"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId") + .HasDatabaseName("IX_job_profile_id"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_ban"); + + b.ToTable("ban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b => + { + b.HasOne("Content.Server.Database.AdminLog", null) + .WithMany("Entities") + .HasForeignKey("AdminLogId", "AdminLogRoundId") + .HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("LogId", "RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerUnban", b => + { + b.HasOne("Content.Server.Database.SqliteServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.SqliteServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_unban_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Entities"); + + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.SqliteServerBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.cs b/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.cs new file mode 100644 index 0000000000..24b4eedc10 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20220108185734_add-species.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class addspecies : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "species", + table: "profile", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "species", + table: "profile"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index e1cc4c9ccf..7839a0e147 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -454,6 +454,11 @@ namespace Content.Server.Database.Migrations.Sqlite .HasColumnType("INTEGER") .HasColumnName("slot"); + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + b.HasKey("Id") .HasName("PK_profile"); diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 98d16e419e..ae5faf2121 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -126,6 +126,7 @@ namespace Content.Server.Database public int Age { get; set; } public string Sex { get; set; } = null!; public string Gender { get; set; } = null!; + public string Species { get; set; } = null!; public string HairName { get; set; } = null!; public string HairColor { get; set; } = null!; public string FacialHairName { get; set; } = null!; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 5fbcf23b78..fd21dcd206 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -158,6 +158,7 @@ namespace Content.Server.Cloning struct ClonerDNAEntry { public Mind.Mind Mind; public HumanoidCharacterProfile Profile; + public ClonerDNAEntry(Mind.Mind m, HumanoidCharacterProfile hcp) { Mind = m; diff --git a/Content.Server/Cloning/Components/CloningPodComponent.cs b/Content.Server/Cloning/Components/CloningPodComponent.cs index 1db2badcf4..0e1484f7c2 100644 --- a/Content.Server/Cloning/Components/CloningPodComponent.cs +++ b/Content.Server/Cloning/Components/CloningPodComponent.cs @@ -8,12 +8,14 @@ using Content.Shared.CharacterAppearance.Systems; using Content.Shared.Cloning; using Content.Shared.MobState.Components; using Content.Shared.Popups; +using Content.Shared.Species; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -24,6 +26,7 @@ namespace Content.Server.Cloning.Components { [Dependency] private readonly IPlayerManager _playerManager = null!; [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly EuiManager _euiManager = null!; @@ -138,7 +141,8 @@ namespace Content.Server.Cloning.Components return; // If we can't track down the client, we can't offer transfer. That'd be quite bad. } - var mob = _entities.SpawnEntity("MobHuman", _entities.GetComponent(Owner).MapPosition); + var speciesProto = _prototype.Index(dna.Profile.Species).Prototype; + var mob = _entities.SpawnEntity(speciesProto, _entities.GetComponent(Owner).MapPosition); EntitySystem.Get().UpdateFromProfile(mob, dna.Profile); diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 1021c9712c..9dcfb05add 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -10,10 +10,13 @@ using Content.Server.Administration.Logs; using Content.Shared.Administration.Logs; using Content.Shared.CharacterAppearance; using Content.Shared.Preferences; +using Content.Shared.Species; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; +using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Database @@ -177,6 +180,7 @@ namespace Content.Server.Database return new HumanoidCharacterProfile( profile.CharacterName, + profile.Species, profile.Age, sex, gender, @@ -204,6 +208,7 @@ namespace Content.Server.Database var entity = new Profile { CharacterName = humanoid.Name, + Species = humanoid.Species, Age = humanoid.Age, Sex = humanoid.Sex.ToString(), Gender = humanoid.Gender.ToString(), diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index cdadded588..071ffa1d14 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -19,6 +19,7 @@ using Content.Shared.Inventory; using Content.Shared.PDA; using Content.Shared.Preferences; using Content.Shared.Roles; +using Content.Shared.Species; using Content.Shared.Station; using Robust.Server.Player; using Robust.Shared.GameObjects; @@ -33,7 +34,6 @@ namespace Content.Server.GameTicking { public partial class GameTicker { - private const string PlayerPrototypeName = "MobHuman"; private const string ObserverPrototypeName = "MobObserver"; [Dependency] private readonly IdCardSystem _cardSystem = default!; @@ -212,7 +212,9 @@ namespace Content.Server.GameTicking private EntityUid SpawnPlayerMob(Job job, HumanoidCharacterProfile? profile, StationId station, bool lateJoin = true) { var coordinates = lateJoin ? GetLateJoinSpawnPoint(station) : GetJobSpawnPoint(job.Prototype.ID, station); - var entity = EntityManager.SpawnEntity(PlayerPrototypeName, coordinates); + var entity = EntityManager.SpawnEntity( + _prototypeManager.Index(profile?.Species ?? SpeciesManager.DefaultSpecies).Prototype, + coordinates); if (job.StartingGear != null) { diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index a5709b9eb9..44041577e8 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -7,6 +7,7 @@ using Content.Shared; using Content.Shared.CCVar; using Content.Shared.Preferences; using Content.Shared.Roles; +using Content.Shared.Species; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.IoC; @@ -262,13 +263,22 @@ namespace Content.Server.Preferences.Managers { case HumanoidCharacterProfile hp: { + var prototypeManager = IoCManager.Resolve(); + var selectedSpecies = SpeciesManager.DefaultSpecies; + + if (prototypeManager.TryIndex(hp.Species, out var species) && species.RoundStart) + { + selectedSpecies = hp.Species; + } + newProf = hp .WithJobPriorities( hp.JobPriorities.Where(job => _protos.HasIndex(job.Key))) .WithAntagPreferences( hp.AntagPreferences.Where(antag => - _protos.HasIndex(antag))); + _protos.HasIndex(antag))) + .WithSpecies(selectedSpecies); break; } default: diff --git a/Content.Shared/Body/Part/BodyPartCompatibility.cs b/Content.Shared/Body/Part/BodyPartCompatibility.cs index d94269989b..91a24819cf 100644 --- a/Content.Shared/Body/Part/BodyPartCompatibility.cs +++ b/Content.Shared/Body/Part/BodyPartCompatibility.cs @@ -4,6 +4,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.Body.Part { + //TODO: This should be a prototype. --DrSmugleaf /// /// Determines whether two s can connect. /// @@ -12,6 +13,7 @@ namespace Content.Shared.Body.Part { Universal = 0, Biological, - Mechanical + Mechanical, + Slime } } diff --git a/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs b/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs index 555072964a..a37a697c0b 100644 --- a/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs +++ b/Content.Shared/CharacterAppearance/Components/HumanoidAppearanceComponent.cs @@ -42,6 +42,14 @@ namespace Content.Shared.CharacterAppearance.Components [ViewVariables] [DataField("canColorFacialHair")] public bool CanColorFacialHair { get; set; } = true; + + [ViewVariables] + [DataField("hairMatchesSkin")] + public bool HairMatchesSkin { get; set; } = false; + + [ViewVariables] + [DataField("hairAlpha")] + public float HairAlpha { get; set; } = 1.0f; } [Serializable, NetSerializable] diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index b7d059c3c8..b4415c51b0 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -8,6 +8,7 @@ using Content.Shared.Dataset; using Content.Shared.GameTicking; using Content.Shared.Random.Helpers; using Content.Shared.Roles; +using Content.Shared.Species; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.IoC; @@ -33,6 +34,7 @@ namespace Content.Shared.Preferences private HumanoidCharacterProfile( string name, + string species, int age, Sex sex, Gender gender, @@ -44,6 +46,7 @@ namespace Content.Shared.Preferences List antagPreferences) { Name = name; + Species = species; Age = age; Sex = sex; Gender = gender; @@ -60,7 +63,7 @@ namespace Content.Shared.Preferences HumanoidCharacterProfile other, Dictionary jobPriorities, List antagPreferences) - : this(other.Name, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack, + : this(other.Name, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack, jobPriorities, other.PreferenceUnavailable, antagPreferences) { } @@ -73,6 +76,7 @@ namespace Content.Shared.Preferences public HumanoidCharacterProfile( string name, + string species, int age, Sex sex, Gender gender, @@ -82,7 +86,7 @@ namespace Content.Shared.Preferences IReadOnlyDictionary jobPriorities, PreferenceUnavailableMode preferenceUnavailable, IReadOnlyList antagPreferences) - : this(name, age, sex, gender, appearance, clothing, backpack, new Dictionary(jobPriorities), + : this(name, species, age, sex, gender, appearance, clothing, backpack, new Dictionary(jobPriorities), preferenceUnavailable, new List(antagPreferences)) { } @@ -91,6 +95,7 @@ namespace Content.Shared.Preferences { return new( "John Doe", + SpeciesManager.DefaultSpecies, MinimumAge, Sex.Male, Gender.Male, @@ -108,6 +113,9 @@ namespace Content.Shared.Preferences public static HumanoidCharacterProfile Random() { var random = IoCManager.Resolve(); + + var species = random.Pick(IoCManager.Resolve() + .EnumeratePrototypes().Where(x => x.RoundStart).ToArray()).ID; var sex = random.Prob(0.5f) ? Sex.Male : Sex.Female; var gender = sex == Sex.Male ? Gender.Male : Gender.Female; @@ -117,7 +125,7 @@ namespace Content.Shared.Preferences var name = $"{firstName} {lastName}"; var age = random.Next(MinimumAge, MaximumAge); - return new HumanoidCharacterProfile(name, age, sex, gender, HumanoidCharacterAppearance.Random(sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, + return new HumanoidCharacterProfile(name, species, age, sex, gender, HumanoidCharacterAppearance.Random(sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, new Dictionary { {SharedGameTicker.FallbackOverflowJob, JobPriority.High} @@ -125,6 +133,7 @@ namespace Content.Shared.Preferences } public string Name { get; private set; } + public string Species { get; private set; } public int Age { get; private set; } public Sex Sex { get; private set; } public Gender Gender { get; private set; } @@ -156,6 +165,12 @@ namespace Content.Shared.Preferences return new(this) { Gender = gender }; } + public HumanoidCharacterProfile WithSpecies(string species) + { + return new(this) { Species = species }; + } + + public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) { return new(this) { Appearance = appearance }; @@ -367,6 +382,7 @@ namespace Content.Shared.Preferences return HashCode.Combine( HashCode.Combine( Name, + Species, Age, Sex, Gender, diff --git a/Content.Shared/Species/SpeciesManager.cs b/Content.Shared/Species/SpeciesManager.cs new file mode 100644 index 0000000000..496b7a29b2 --- /dev/null +++ b/Content.Shared/Species/SpeciesManager.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Species; + +public class SpeciesManager +{ + public const string DefaultSpecies = "Human"; +} diff --git a/Content.Shared/Species/SpeciesPrototype.cs b/Content.Shared/Species/SpeciesPrototype.cs new file mode 100644 index 0000000000..a1b2fbe56b --- /dev/null +++ b/Content.Shared/Species/SpeciesPrototype.cs @@ -0,0 +1,53 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Species; + +[Prototype("species")] +public class SpeciesPrototype : IPrototype +{ + /// + /// Prototype ID of the species. + /// + [DataField("id", required: true)] + public string ID { get; } = default!; + + /// + /// User visible name of the species. + /// + [DataField("name", required: true)] + public string Name { get; } = default!; + + /// + /// Whether the species is available "at round start" (In the character editor) + /// + [DataField("roundStart", required: true)] + public bool RoundStart { get; } = false; + + /// + /// Prototype used by the species as a body. + /// + [DataField("prototype", required: true)] + public string Prototype { get; } = default!; + + /// + /// Prototype used by the species for the dress-up doll in various menus. + /// + [DataField("dollPrototype", required: true)] + public string DollPrototype { get; } = default!; + + /// + /// Method of skin coloration used by the species. + /// + [DataField("skinColoration", required: true)] + public SpeciesSkinColor SkinColoration { get; } + + +} + +public enum SpeciesSkinColor +{ + HumanToned, + Hues, +} diff --git a/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs b/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs index 5fd821988c..9bf1df1d16 100644 --- a/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs +++ b/Content.Tests/Server/Preferences/ServerDbSqliteTests.cs @@ -42,6 +42,7 @@ namespace Content.Tests.Server.Preferences { return new( "Charlie Charlieson", + "Human", 21, Sex.Male, Gender.Epicene, diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl index b6fadc2d50..99957b02a9 100644 --- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl +++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl @@ -7,6 +7,7 @@ humanoid-profile-editor-sex-male-button = Male humanoid-profile-editor-sex-female-button = Female humanoid-profile-editor-age-label = Age: humanoid-profile-editor-skin-color-label = Skin color: +humanoid-profile-editor-species-label = Species: humanoid-profile-editor-pronouns-label = Pronouns: humanoid-profile-editor-pronouns-male-text = He / Him humanoid-profile-editor-pronouns-female-text = She / Her diff --git a/Resources/Prototypes/Body/Mechanisms/slime.yml b/Resources/Prototypes/Body/Mechanisms/slime.yml new file mode 100644 index 0000000000..add05c09bf --- /dev/null +++ b/Resources/Prototypes/Body/Mechanisms/slime.yml @@ -0,0 +1,28 @@ +- type: entity + id: SentientSlimeCore + parent: BaseItem + name: sentient slime core + description: "The source of incredible, unending gooeyness." + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/Human/organs.rsi + state: brain + - type: Mechanism + size: 5 + compatibility: Slime + - type: Lung + - type: Brain + - type: Stomach + maxVolume: 250 + digestionDelay: 20 + - type: Metabolizer + maxReagents: 6 + metabolizerTypes: [ Slime ] + removeEmpty: true + groups: + - id: Food + - id: Drink + - id: Medicine + - id: Poison + - id: Narcotic diff --git a/Resources/Prototypes/Body/Parts/slime.yml b/Resources/Prototypes/Body/Parts/slime.yml index 31b2fcc493..c43f42ba38 100644 --- a/Resources/Prototypes/Body/Parts/slime.yml +++ b/Resources/Prototypes/Body/Parts/slime.yml @@ -23,13 +23,9 @@ - type: BodyPart partType: Torso size: 14 - compatibility: Biological + compatibility: Slime mechanisms: - - OrganHumanHeart - - OrganHumanLungs - - OrganHumanStomach - - OrganHumanLiver - - OrganHumanKidneys + - SentientSlimeCore # criticalThreshold: 100 # deadThreshold: 150 @@ -48,11 +44,8 @@ - type: BodyPart partType: Head size: 7 - compatibility: Biological + compatibility: Slime vital: true - mechanisms: - - OrganHumanBrain - - OrganHumanEyes # criticalThreshold: 50 # deadThreshold: 120 - type: Input @@ -75,7 +68,7 @@ - type: BodyPart partType: Arm size: 5 - compatibility: Biological + compatibility: Slime symmetry: Left # criticalThreshold: 40 # deadThreshold: 80 @@ -95,7 +88,7 @@ - type: BodyPart partType: Arm size: 5 - compatibility: Biological + compatibility: Slime symmetry: Right # criticalThreshold: 40 # deadThreshold: 80 @@ -115,7 +108,7 @@ - type: BodyPart partType: Hand size: 3 - compatibility: Biological + compatibility: Slime symmetry: Left # criticalThreshold: 30 # deadThreshold: 60 @@ -135,7 +128,7 @@ - type: BodyPart partType: Hand size: 3 - compatibility: Biological + compatibility: Slime symmetry: Right # criticalThreshold: 30 # deadThreshold: 60 @@ -155,7 +148,7 @@ - type: BodyPart partType: Leg size: 6 - compatibility: Biological + compatibility: Slime symmetry: Left - type: entity @@ -173,7 +166,7 @@ - type: BodyPart partType: Leg size: 6 - compatibility: Biological + compatibility: Slime symmetry: Right # criticalThreshold: 45 # deadThreshold: 90 @@ -193,7 +186,7 @@ - type: BodyPart partType: Foot size: 2 - compatibility: Biological + compatibility: Slime symmetry: Left # criticalThreshold: 30 # deadThreshold: 60 @@ -213,7 +206,7 @@ - type: BodyPart partType: Foot size: 2 - compatibility: Biological + compatibility: Slime symmetry: Right # criticalThreshold: 30 # deadThreshold: 60 diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml index 450e1ca3d4..db7a66401a 100644 --- a/Resources/Prototypes/Chemistry/metabolizer_types.yml +++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml @@ -6,3 +6,6 @@ - type: metabolizerType id: Animal + +- type: metabolizerType + id: Slime diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml new file mode 100644 index 0000000000..d8ea943b26 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -0,0 +1,26 @@ +- type: entity + save: false + name: Urist McHands The Dwarf + parent: MobDwarfBase + id: MobDwarf + description: A miserable pile of secrets. + components: + - type: Mind + showExamineInfo: true + - type: Input + context: "human" + - type: PlayerMobMover + - type: PlayerInputMover + - type: Alerts + - type: Actions + innateActions: + - CombatMode + - Disarm + - HumanScream + - type: Eye + - type: CameraRecoil + - type: Examiner + - type: CanHostGuardian + - type: AiFactionTag + factions: + - NanoTrasen diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index ae4406be9b..34b22801ac 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -1,20 +1,24 @@ - type: entity save: false - parent: BaseSlimePerson - id: SlimePerson + parent: MobSlimePersonBase + id: MobSlimePerson components: - type: Mind showExamineInfo: true - type: Input context: "human" + - type: PlayerMobMover + - type: PlayerInputMover - type: Alerts - type: Actions innateActions: - - HumanScream + - CombatMode - Disarm + - HumanScream - type: Eye - type: CameraRecoil - type: Examiner + - type: CanHostGuardian - type: AiFactionTag factions: - NanoTrasen diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml new file mode 100644 index 0000000000..0a0cf8d6c6 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -0,0 +1,192 @@ +- type: entity + save: false + name: Urist McHands The Dwarf + parent: MobHumanBase + id: MobDwarfBase + abstract: true + description: A miserable pile of secrets. + components: + - type: Icon + sprite: Mobs/Species/Slime/parts.rsi + state: full + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 1, 0.8 + layers: + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_leg + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: [ "enum.HumanoidVisualLayers.StencilMask" ] + sprite: Mobs/Customization/masking_helpers.rsi + state: female_full + visible: false + - map: [ "jumpsuit" ] + shader: StencilDraw + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.LFoot" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_foot + - map: [ "enum.HumanoidVisualLayers.RFoot" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_foot + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.FacialHair" ] + state: shaved + sprite: Mobs/Customization/human_facial_hair.rsi + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - type: Body + template: HumanoidTemplate + preset: HumanPreset + - type: DoorBumpOpener + +- type: entity + save: false + name: Urist McHands + parent: MobHumanDummy + id: MobDwarfDummy + abstract: true + description: A dummy human meant to be used in character setup. + components: + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 1, 0.8 + layers: + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_arm + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_leg + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: [ "enum.HumanoidVisualLayers.StencilMask" ] + sprite: Mobs/Customization/masking_helpers.rsi + state: female_full + visible: false + - map: [ "jumpsuit" ] + shader: StencilDraw + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.LFoot" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_foot + - map: [ "enum.HumanoidVisualLayers.RFoot" ] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_foot + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.FacialHair" ] + state: shaved + sprite: Mobs/Customization/human_facial_hair.rsi + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index b787424d9d..d86cabcad8 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -1,7 +1,7 @@ - type: entity - name: Slime Person - parent: MobHuman - id: BaseSlimePerson + name: Urist McSlime + parent: MobHumanBase + id: MobSlimePersonBase description: A miserable pile of slime. abstract: true components: @@ -94,3 +94,102 @@ template: HumanoidTemplate preset: SlimePreset - type: DoorBumpOpener + - type: HumanoidAppearance + hairMatchesSkin: true + hairAlpha: 0.5 + +- type: entity + save: false + name: Urist McHands + parent: MobHumanDummy + id: MobSlimePersonDummy + abstract: true + description: A dummy slime meant to be used in character setup. + components: + - type: Sprite + netsync: false + drawdepth: Mobs + layers: + - map: [ "enum.HumanoidVisualLayers.Chest" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: torso_m + - map: [ "enum.HumanoidVisualLayers.Head" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: head_m + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes + - map: [ "enum.HumanoidVisualLayers.RArm" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: r_arm + - map: [ "enum.HumanoidVisualLayers.LArm" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: l_arm + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: r_leg + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: l_leg + - shader: StencilClear + sprite: Mobs/Species/Slime/parts.rsi + state: l_leg + - shader: StencilMask + map: [ "enum.HumanoidVisualLayers.StencilMask" ] + sprite: Mobs/Customization/masking_helpers.rsi + state: female_full + visible: false + - map: [ "jumpsuit" ] + shader: StencilDraw + - map: [ "enum.HumanoidVisualLayers.LHand" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: l_hand + - map: [ "enum.HumanoidVisualLayers.RHand" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: r_hand + - map: [ "enum.HumanoidVisualLayers.LFoot" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: l_foot + - map: [ "enum.HumanoidVisualLayers.RFoot" ] + color: "#b8b8b8" + sprite: Mobs/Species/Slime/parts.rsi + state: r_foot + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "id" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.FacialHair" ] + state: shaved + sprite: Mobs/Customization/human_facial_hair.rsi + color: '#FFFFFF80' + - map: [ "enum.HumanoidVisualLayers.Hair" ] + state: bald + sprite: Mobs/Customization/human_hair.rsi + color: '#FFFFFF80' + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - type: HumanoidAppearance + hairMatchesSkin: true + hairAlpha: 0.5 diff --git a/Resources/Prototypes/species.yml b/Resources/Prototypes/species.yml new file mode 100644 index 0000000000..8bb50c8664 --- /dev/null +++ b/Resources/Prototypes/species.yml @@ -0,0 +1,23 @@ +- type: species + id: Human + name: Human + roundStart: true + prototype: MobHuman + dollPrototype: MobHumanDummy + skinColoration: HumanToned + +- type: species + id: Dwarf + name: Dwarf + roundStart: true + prototype: MobDwarf + dollPrototype: MobDwarfDummy + skinColoration: HumanToned + +- type: species + id: SlimePerson + name: Slime Person + roundStart: true + prototype: MobSlimePerson + dollPrototype: MobSlimePersonDummy + skinColoration: Hues