From c4ea6e53e80a4b52335961da1d45e63bb0645b2e Mon Sep 17 00:00:00 2001 From: DamianX Date: Wed, 15 Jan 2020 15:10:18 +0100 Subject: [PATCH] Use EFCore to store preferences (#506) * Use EFcore to store preferences * Fixed nullabilty warnings --- .../Content.Server.Database.csproj | 28 +++ .../20200111103836_InitialCreate.Designer.cs | 109 +++++++++ .../20200111103836_InitialCreate.cs | 71 ++++++ .../PreferencesDbContextModelSnapshot.cs | 105 +++++++++ Content.Server.Database/Model.cs | 51 +++++ Content.Server.Database/PrefsDb.cs | 60 +++++ Content.Server/Content.Server.csproj | 6 +- .../Preferences/Migrations/000_Initial.sql | 20 -- .../Migrations/MigrationManager.cs | 169 -------------- .../Preferences/PreferencesDatabase.cs | 206 ++++-------------- SpaceStation14.sln | 6 + SpaceStation14.sln.DotSettings | 1 + 12 files changed, 473 insertions(+), 359 deletions(-) create mode 100644 Content.Server.Database/Content.Server.Database.csproj create mode 100644 Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs create mode 100644 Content.Server.Database/Migrations/20200111103836_InitialCreate.cs create mode 100644 Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs create mode 100644 Content.Server.Database/Model.cs create mode 100644 Content.Server.Database/PrefsDb.cs delete mode 100644 Content.Server/Preferences/Migrations/000_Initial.sql delete mode 100644 Content.Server/Preferences/Migrations/MigrationManager.cs diff --git a/Content.Server.Database/Content.Server.Database.csproj b/Content.Server.Database/Content.Server.Database.csproj new file mode 100644 index 0000000000..469168a8e8 --- /dev/null +++ b/Content.Server.Database/Content.Server.Database.csproj @@ -0,0 +1,28 @@ + + + + + $(TargetFramework) + 8 + false + x64 + false + ..\bin\Content.Server.Database\ + true + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs b/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs new file mode 100644 index 0000000000..3d5f2d5b1d --- /dev/null +++ b/Content.Server.Database/Migrations/20200111103836_InitialCreate.Designer.cs @@ -0,0 +1,109 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations +{ + [DbContext(typeof(PreferencesDbContext))] + [Migration("20200111103836_InitialCreate")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0"); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.Property("HumanoidProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PrefsId") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnType("INTEGER"); + + b.Property("SlotName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("HumanoidProfileId"); + + b.HasIndex("PrefsId"); + + b.ToTable("HumanoidProfile"); + }); + + modelBuilder.Entity("Content.Server.Database.Prefs", b => + { + b.Property("PrefsId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("PrefsId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.HasOne("Content.Server.Database.Prefs", null) + .WithMany("HumanoidProfiles") + .HasForeignKey("PrefsId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs b/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs new file mode 100644 index 0000000000..1324d3ca88 --- /dev/null +++ b/Content.Server.Database/Migrations/20200111103836_InitialCreate.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations +{ + public partial class InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + "Preferences", + table => new + { + PrefsId = table.Column() + .Annotation("Sqlite:Autoincrement", true), + Username = table.Column(), + SelectedCharacterSlot = table.Column() + }, + constraints: table => { table.PrimaryKey("PK_Preferences", x => x.PrefsId); }); + + migrationBuilder.CreateTable( + "HumanoidProfile", + table => new + { + HumanoidProfileId = table.Column() + .Annotation("Sqlite:Autoincrement", true), + Slot = table.Column(), + SlotName = table.Column(), + CharacterName = table.Column(), + Age = table.Column(), + Sex = table.Column(), + HairName = table.Column(), + HairColor = table.Column(), + FacialHairName = table.Column(), + FacialHairColor = table.Column(), + EyeColor = table.Column(), + SkinColor = table.Column(), + PrefsId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_HumanoidProfile", x => x.HumanoidProfileId); + table.ForeignKey( + "FK_HumanoidProfile_Preferences_PrefsId", + x => x.PrefsId, + "Preferences", + "PrefsId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + "IX_HumanoidProfile_PrefsId", + "HumanoidProfile", + "PrefsId"); + + migrationBuilder.CreateIndex( + "IX_Preferences_Username", + "Preferences", + "Username", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + "HumanoidProfile"); + + migrationBuilder.DropTable( + "Preferences"); + } + } +} diff --git a/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs new file mode 100644 index 0000000000..1cdebf3e2e --- /dev/null +++ b/Content.Server.Database/Migrations/PreferencesDbContextModelSnapshot.cs @@ -0,0 +1,105 @@ +// + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Content.Server.Database.Migrations +{ + [DbContext(typeof(PreferencesDbContext))] + internal class PreferencesDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0"); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.Property("HumanoidProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PrefsId") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnType("INTEGER"); + + b.Property("SlotName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("HumanoidProfileId"); + + b.HasIndex("PrefsId"); + + b.ToTable("HumanoidProfile"); + }); + + modelBuilder.Entity("Content.Server.Database.Prefs", b => + { + b.Property("PrefsId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("PrefsId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.HasOne("Content.Server.Database.Prefs", null) + .WithMany("HumanoidProfiles") + .HasForeignKey("PrefsId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs new file mode 100644 index 0000000000..54b4fce67a --- /dev/null +++ b/Content.Server.Database/Model.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace Content.Server.Database +{ + public class PreferencesDbContext : DbContext + { + // This is used by the "dotnet ef" CLI tool. + public PreferencesDbContext() : + base(new DbContextOptionsBuilder().UseSqlite("Data Source=:memory:").Options) + { + } + + public PreferencesDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Preferences { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasIndex(p => p.Username) + .IsUnique(); + } + } + + public class Prefs + { + public int PrefsId { get; set; } + public string Username { get; set; } = null!; + public int SelectedCharacterSlot { get; set; } + public List HumanoidProfiles { get; } = new List(); + } + + public class HumanoidProfile + { + public int HumanoidProfileId { get; set; } + public int Slot { get; set; } + public string SlotName { get; set; } = null!; + public string CharacterName { get; set; } = null!; + public int Age { get; set; } + public string Sex { get; set; } = null!; + public string HairName { get; set; } = null!; + public string HairColor { get; set; } = null!; + public string FacialHairName { get; set; } = null!; + public string FacialHairColor { get; set; } = null!; + public string EyeColor { get; set; } = null!; + public string SkinColor { get; set; } = null!; + } +} diff --git a/Content.Server.Database/PrefsDb.cs b/Content.Server.Database/PrefsDb.cs new file mode 100644 index 0000000000..506686dccc --- /dev/null +++ b/Content.Server.Database/PrefsDb.cs @@ -0,0 +1,60 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Content.Server.Database +{ + public class PrefsDb + { + private readonly PreferencesDbContext _prefsCtx; + + public PrefsDb(string dbPath) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite($"Data Source={dbPath}"); + + _prefsCtx = new PreferencesDbContext(optionsBuilder.Options); + _prefsCtx.Database.Migrate(); + } + + public Prefs GetPlayerPreferences(string username) + { + return _prefsCtx.Preferences.SingleOrDefault(p => p.Username == username); + } + + public void SaveSelectedCharacterIndex(string username, int slot) + { + var prefs = _prefsCtx.Preferences.SingleOrDefault(p => p.Username == username); + if (prefs is null) + _prefsCtx.Preferences.Add(new Prefs + { + Username = username, + SelectedCharacterSlot = slot + }); + else + prefs.SelectedCharacterSlot = slot; + _prefsCtx.SaveChanges(); + } + + public void SaveCharacterSlot(string username, HumanoidProfile newProfile) + { + var prefs = _prefsCtx + .Preferences + .Single(p => p.Username == username); + var oldProfile = prefs + .HumanoidProfiles + .SingleOrDefault(h => h.Slot == newProfile.Slot); + if (!(oldProfile is null)) prefs.HumanoidProfiles.Remove(oldProfile); + prefs.HumanoidProfiles.Add(newProfile); + } + + public void DeleteCharacterSlot(string username, int slot) + { + var profile = _prefsCtx + .Preferences + .Single(p => p.Username == username) + .HumanoidProfiles + .RemoveAll(h => h.Slot == slot); + _prefsCtx.SaveChanges(); + } + } +} diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 7d7ba55760..948ae862a2 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -12,12 +12,11 @@ - - + false @@ -32,7 +31,4 @@ - - - diff --git a/Content.Server/Preferences/Migrations/000_Initial.sql b/Content.Server/Preferences/Migrations/000_Initial.sql deleted file mode 100644 index 00c261a696..0000000000 --- a/Content.Server/Preferences/Migrations/000_Initial.sql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE TABLE IF NOT EXISTS "HumanoidCharacterProfiles" ( - "Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, - "Player" INTEGER NOT NULL, - "Slot" INTEGER NOT NULL, - "Name" TEXT NOT NULL, - "Age" INTEGER NOT NULL, - "Sex" TEXT NOT NULL, - "HairStyleName" TEXT NOT NULL, - "HairColor" TEXT NOT NULL, - "FacialHairStyleName" TEXT NOT NULL, - "FacialHairColor" TEXT NOT NULL, - "EyeColor" TEXT NOT NULL, - "SkinColor" TEXT NOT NULL, - FOREIGN KEY("Player") REFERENCES "PlayerPreferences"("Id") -); -CREATE TABLE IF NOT EXISTS "PlayerPreferences" ( - "Id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, - "Username" TEXT NOT NULL UNIQUE, - "SelectedCharacterIndex" INTEGER NOT NULL -); diff --git a/Content.Server/Preferences/Migrations/MigrationManager.cs b/Content.Server/Preferences/Migrations/MigrationManager.cs deleted file mode 100644 index 9057d102dc..0000000000 --- a/Content.Server/Preferences/Migrations/MigrationManager.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using Dapper; -using JetBrains.Annotations; -using Microsoft.Data.Sqlite; -using Robust.Shared.Log; - -namespace Content.Server.Preferences.Migrations -{ - /// - /// Ensures database schemas are up to date. - /// - public static class MigrationManager - { - /// - /// Ensures the database schema for the given connection string is up to date. - /// - public static void PerformUpgrade(string connectionString) - { - using (var connection = new SqliteConnection(connectionString)) - { - EnsureSchemaVersionTableExists(connection); - foreach (var migrationToRun in MigrationsToRun(connection)) - { - Logger.InfoS("db", "Running migration {0}", migrationToRun.Id); - migrationToRun.Run(connection); - } - } - } - - /// - /// Generated for each SQL file found. - /// - private class Migration - { - public readonly string Id; - private readonly string _sql; - - public Migration(string id, string sql) - { - Id = id; - _sql = sql; - } - - /// - /// Executes the query in and logs this in the SchemaVersion table. - /// - public void Run(IDbConnection connection) - { - connection.Execute(_sql); - InsertMigrationLog(connection, Id); - } - } - - private const string InsertMigrationLogQuery = - @"INSERT INTO SchemaVersion (Id) VALUES (@Id)"; - /// - /// Inserts a in the SchemaVersion table. - /// - private static void InsertMigrationLog(IDbConnection connection, string id) - { - Logger.InfoS("db", "Completing migration {0}", id); - connection.Execute(InsertMigrationLogQuery, new {Id = id}); - } - - /// - /// An entry in the SchemaVersion table. - /// - [UsedImplicitly] - private class MigrationLog - { - public string Id; - public string Timestamp; - } - - private const string GetRanMigrationsQuery = - @"SELECT Id, Timestamp FROM SchemaVersion ORDER BY Id COLLATE NOCASE"; - /// - /// Fetches a collection of from the SchemaVersion table and returns it. - /// - private static IEnumerable RanMigrations(IDbConnection connection) - { - return connection.Query(GetRanMigrationsQuery); - } - - /// - /// Finds all available migrations, returns those that haven't been run yet. - /// - private static List MigrationsToRun(IDbConnection connection) - { - var discoveredMigrations = DiscoverMigrations(connection); - if (discoveredMigrations.Count == 0) - { - // No migrations found. - return null; - } - - var ranMigrations = RanMigrations(connection); - - // Filter out migrations that have already been executed - discoveredMigrations - .RemoveAll(migration => ranMigrations.Any(ranMigration => migration.Id == ranMigration.Id)); - return discoveredMigrations; - } - - /// - /// Given an embedded resource's full path returns its contents as a string. - /// - private static string ResourceAssemblyToString(string resourceName) - { - using (var stream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream(resourceName)) - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd(); - } - } - - /// - /// Searches the current assembly for SQL migration files. - /// TODO: Filter by subfolder so that different databases use different sets of migrations. - /// - [NotNull] - private static List DiscoverMigrations(IDbConnection connection) - { - var results = new List(); - var assembly = Assembly.GetExecutingAssembly(); - foreach (var sqlResourceName in assembly - .GetManifestResourceNames() - .Where(IsValidMigrationFileName)) - { - var splitName = sqlResourceName.Split('.'); - // The second to last string in the list is the actual file name without the final ".sql" - var migrationId = splitName[splitName.Length - 2]; - var sqlContents = ResourceAssemblyToString(sqlResourceName); - results.Add(new Migration(migrationId, sqlContents)); - } - - return results; - } - - /// - /// A valid file name is "000_Initial.sql". A dot (from the path, not to be included in the filename itself), - /// three digits, a mandatory underscore, any number of characters, a mandatory ".sql". - /// - private static bool IsValidMigrationFileName(string name) - { - return Regex.IsMatch(name, @"\.\d\d\d_[a-zA-Z]+\.sql$"); - } - - private const string EnsureSchemaVersionTableExistsQuery = - @"CREATE TABLE IF NOT EXISTS SchemaVersion ( - Id TEXT NOT NULL UNIQUE, - Timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP - )"; - - /// - /// Creates the SchemaVersion table if it doesn't exist. - /// - private static void EnsureSchemaVersionTableExists(IDbConnection connection) - { - connection.Execute(EnsureSchemaVersionTableExistsQuery); - } - } -} diff --git a/Content.Server/Preferences/PreferencesDatabase.cs b/Content.Server/Preferences/PreferencesDatabase.cs index 7e8eb0201c..e806fa27f9 100644 --- a/Content.Server/Preferences/PreferencesDatabase.cs +++ b/Content.Server/Preferences/PreferencesDatabase.cs @@ -1,171 +1,63 @@ using System; using System.Linq; -using Content.Server.Preferences.Migrations; +using Content.Server.Database; using Content.Shared.Preferences; -using Dapper; -using Microsoft.Data.Sqlite; using Robust.Shared.Maths; using static Content.Shared.Preferences.Sex; namespace Content.Server.Preferences { /// - /// Provides methods to retrieve and update character preferences. - /// Don't use this directly, go through instead. + /// Provides methods to retrieve and update character preferences. + /// Don't use this directly, go through instead. /// public class PreferencesDatabase { - private readonly string _databaseFilePath; private readonly int _maxCharacterSlots; + private readonly PrefsDb _prefsDb; public PreferencesDatabase(string databaseFilePath, int maxCharacterSlots) { - _databaseFilePath = databaseFilePath; _maxCharacterSlots = maxCharacterSlots; - MigrationManager.PerformUpgrade(GetDbConnectionString()); - } - - private string GetDbConnectionString() - { - return new SqliteConnectionStringBuilder - { - DataSource = _databaseFilePath, - }.ToString(); - } - - private SqliteConnection GetDbConnection() - { - var connectionString = GetDbConnectionString(); - var conn = new SqliteConnection(connectionString); - conn.Open(); - return conn; - } - - private const string PlayerPreferencesQuery = - @"SELECT Id, SelectedCharacterIndex FROM PlayerPreferences WHERE Username=@Username"; - - private const string HumanoidCharactersQuery = - @"SELECT Slot, Name, Age, Sex, HairStyleName, HairColor, FacialHairStyleName, FacialHairColor, EyeColor, SkinColor - FROM HumanoidCharacterProfiles - WHERE Player = @Id"; - - private sealed class PlayerPreferencesSql - { - public int Id { get; set; } - public int SelectedCharacterIndex { get; set; } + _prefsDb = new PrefsDb(databaseFilePath); } public PlayerPreferences GetPlayerPreferences(string username) { - using (var connection = GetDbConnection()) - { - var prefs = connection.QueryFirstOrDefault( - PlayerPreferencesQuery, - new {Username = username}); - if (prefs is null) - { - return null; - } + var prefs = _prefsDb.GetPlayerPreferences(username); + if (prefs is null) return null; - // Using Dapper for ICharacterProfile and ICharacterAppearance is annoying so - // we do it manually - var cmd = new SqliteCommand(HumanoidCharactersQuery, connection); - cmd.Parameters.AddWithValue("@Id", prefs.Id); - cmd.Prepare(); - - var reader = cmd.ExecuteReader(); - var profiles = new ICharacterProfile[_maxCharacterSlots]; - while (reader.Read()) + var profiles = new ICharacterProfile[_maxCharacterSlots]; + foreach (var profile in prefs.HumanoidProfiles) + profiles[profile.Slot] = new HumanoidCharacterProfile { - profiles[reader.GetInt32(0)] = new HumanoidCharacterProfile + Name = profile.CharacterName, + Age = profile.Age, + Sex = profile.Sex == "Male" ? Male : Female, + CharacterAppearance = new HumanoidCharacterAppearance { - Name = reader.GetString(1), - Age = reader.GetInt32(2), - Sex = reader.GetString(3) == "Male" ? Male : Female, - CharacterAppearance = new HumanoidCharacterAppearance - { - HairStyleName = reader.GetString(4), - HairColor = Color.FromHex(reader.GetString(5)), - FacialHairStyleName = reader.GetString(6), - FacialHairColor = Color.FromHex(reader.GetString(7)), - EyeColor = Color.FromHex(reader.GetString(8)), - SkinColor = Color.FromHex(reader.GetString(9)) - } - }; - } - - return new PlayerPreferences - { - SelectedCharacterIndex = prefs.SelectedCharacterIndex, - Characters = profiles.ToList() + HairStyleName = profile.HairName, + HairColor = Color.FromHex(profile.HairColor), + FacialHairStyleName = profile.FacialHairName, + FacialHairColor = Color.FromHex(profile.FacialHairColor), + EyeColor = Color.FromHex(profile.EyeColor), + SkinColor = Color.FromHex(profile.SkinColor) + } }; - } + + return new PlayerPreferences + { + SelectedCharacterIndex = prefs.SelectedCharacterSlot, + Characters = profiles.ToList() + }; } - private const string SaveSelectedCharacterIndexQuery = - @"UPDATE PlayerPreferences - SET SelectedCharacterIndex = @SelectedCharacterIndex - WHERE Username = @Username; - - -- If no update happened (i.e. the row didn't exist) then insert one // https://stackoverflow.com/a/38463024 - INSERT INTO PlayerPreferences - (SelectedCharacterIndex, Username) - SELECT - @SelectedCharacterIndex, - @Username - WHERE (SELECT Changes() = 0);"; - public void SaveSelectedCharacterIndex(string username, int index) { index = index.Clamp(0, _maxCharacterSlots - 1); - using (var connection = GetDbConnection()) - { - connection.Execute(SaveSelectedCharacterIndexQuery, - new {SelectedCharacterIndex = index, Username = username}); - } + _prefsDb.SaveSelectedCharacterIndex(username, index); } - private const string SaveCharacterSlotQuery = - @"UPDATE HumanoidCharacterProfiles - SET - Name = @Name, - Age = @Age, - Sex = @Sex, - HairStyleName = @HairStyleName, - HairColor = @HairColor, - FacialHairStyleName = @FacialHairStyleName, - FacialHairColor = @FacialHairColor, - EyeColor = @EyeColor, - SkinColor = @SkinColor - WHERE Slot = @Slot AND Player = (SELECT Id FROM PlayerPreferences WHERE Username = @Username); - - -- If no update happened (i.e. the row didn't exist) then insert one // https://stackoverflow.com/a/38463024 - INSERT INTO HumanoidCharacterProfiles - (Slot, - Player, - Name, - Age, - Sex, - HairStyleName, - HairColor, - FacialHairStyleName, - FacialHairColor, - EyeColor, - SkinColor) - SELECT - @Slot, - (SELECT Id FROM PlayerPreferences WHERE Username = @Username), - @Name, - @Age, - @Sex, - @HairStyleName, - @HairColor, - @FacialHairStyleName, - @FacialHairColor, - @EyeColor, - @SkinColor - WHERE (SELECT Changes() = 0);"; - public void SaveCharacterSlot(string username, ICharacterProfile profile, int slot) { if (slot < 0 || slot >= _maxCharacterSlots) @@ -177,44 +69,28 @@ namespace Content.Server.Preferences } if (!(profile is HumanoidCharacterProfile humanoid)) - { // TODO: Handle other ICharacterProfile implementations properly throw new NotImplementedException(); - } var appearance = (HumanoidCharacterAppearance) humanoid.CharacterAppearance; - using (var connection = GetDbConnection()) + _prefsDb.SaveCharacterSlot(username, new HumanoidProfile { - connection.Execute(SaveCharacterSlotQuery, new - { - Name = humanoid.Name, - Age = humanoid.Age, - Sex = humanoid.Sex.ToString(), - HairStyleName = appearance.HairStyleName, - HairColor = appearance.HairColor.ToHex(), - FacialHairStyleName = appearance.FacialHairStyleName, - FacialHairColor = appearance.FacialHairColor.ToHex(), - EyeColor = appearance.EyeColor.ToHex(), - SkinColor = appearance.SkinColor.ToHex(), - Slot = slot, - Username = username - }); - } + CharacterName = humanoid.Name, + Age = humanoid.Age, + Sex = humanoid.Sex.ToString(), + HairName = appearance.HairStyleName, + HairColor = appearance.HairColor.ToHex(), + FacialHairName = appearance.FacialHairStyleName, + FacialHairColor = appearance.FacialHairColor.ToHex(), + EyeColor = appearance.EyeColor.ToHex(), + SkinColor = appearance.SkinColor.ToHex(), + Slot = slot + }); } - private const string DeleteCharacterSlotQuery = - @"DELETE FROM HumanoidCharacterProfiles - WHERE - Player = (SELECT Id FROM PlayerPreferences WHERE Username = @Username) - AND - Slot = @Slot"; - private void DeleteCharacterSlot(string username, int slot) { - using (var connection = GetDbConnection()) - { - connection.Execute(DeleteCharacterSlotQuery, new {Username = username, Slot = slot}); - } + _prefsDb.DeleteCharacterSlot(username, slot); } } } diff --git a/SpaceStation14.sln b/SpaceStation14.sln index 6f4a3a5fa5..cfb56ada0b 100644 --- a/SpaceStation14.sln +++ b/SpaceStation14.sln @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Benchmarks", "Conte EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenToolkit.GraphicsLibraryFramework", "RobustToolbox\OpenToolkit.GraphicsLibraryFramework\OpenToolkit.GraphicsLibraryFramework.csproj", "{4809F412-3132-419E-BF9D-CCF7593C3533}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Content.Server.Database", "Content.Server.Database\Content.Server.Database.csproj", "{45C9B43F-305D-4651-9863-F6384CBC847F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -104,6 +106,10 @@ Global {4809F412-3132-419E-BF9D-CCF7593C3533}.Debug|x64.Build.0 = Debug|x64 {4809F412-3132-419E-BF9D-CCF7593C3533}.Release|x64.ActiveCfg = Release|x64 {4809F412-3132-419E-BF9D-CCF7593C3533}.Release|x64.Build.0 = Release|x64 + {45C9B43F-305D-4651-9863-F6384CBC847F}.Debug|x64.ActiveCfg = Debug|Any CPU + {45C9B43F-305D-4651-9863-F6384CBC847F}.Debug|x64.Build.0 = Debug|Any CPU + {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|x64.ActiveCfg = Release|Any CPU + {45C9B43F-305D-4651-9863-F6384CBC847F}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 8ca2ed5f1d..523a83e417 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -21,5 +21,6 @@ True True True + True True True