Use EFCore to store preferences (#506)

* Use EFcore to store preferences

* Fixed nullabilty warnings
This commit is contained in:
DamianX
2020-01-15 15:10:18 +01:00
committed by Pieter-Jan Briers
parent 1856cb079c
commit c4ea6e53e8
12 changed files with 473 additions and 359 deletions

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets"/>
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>8</LangVersion>
<IsPackable>false</IsPackable>
<Platforms>x64</Platforms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Server.Database\</OutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<Nullable>enable</Nullable>
</PropertyGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets"/>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0"/>
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,109 @@
// <auto-generated />
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<int>("HumanoidProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Age")
.HasColumnType("INTEGER");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("PrefsId")
.HasColumnType("INTEGER");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Slot")
.HasColumnType("INTEGER");
b.Property<string>("SlotName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("HumanoidProfileId");
b.HasIndex("PrefsId");
b.ToTable("HumanoidProfile");
});
modelBuilder.Entity("Content.Server.Database.Prefs", b =>
{
b.Property<int>("PrefsId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER");
b.Property<string>("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
}
}
}

View File

@@ -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<int>()
.Annotation("Sqlite:Autoincrement", true),
Username = table.Column<string>(),
SelectedCharacterSlot = table.Column<int>()
},
constraints: table => { table.PrimaryKey("PK_Preferences", x => x.PrefsId); });
migrationBuilder.CreateTable(
"HumanoidProfile",
table => new
{
HumanoidProfileId = table.Column<int>()
.Annotation("Sqlite:Autoincrement", true),
Slot = table.Column<int>(),
SlotName = table.Column<string>(),
CharacterName = table.Column<string>(),
Age = table.Column<int>(),
Sex = table.Column<string>(),
HairName = table.Column<string>(),
HairColor = table.Column<string>(),
FacialHairName = table.Column<string>(),
FacialHairColor = table.Column<string>(),
EyeColor = table.Column<string>(),
SkinColor = table.Column<string>(),
PrefsId = table.Column<int>(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");
}
}
}

View File

@@ -0,0 +1,105 @@
// <auto-generated />
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<int>("HumanoidProfileId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Age")
.HasColumnType("INTEGER");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("PrefsId")
.HasColumnType("INTEGER");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Slot")
.HasColumnType("INTEGER");
b.Property<string>("SlotName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("HumanoidProfileId");
b.HasIndex("PrefsId");
b.ToTable("HumanoidProfile");
});
modelBuilder.Entity("Content.Server.Database.Prefs", b =>
{
b.Property<int>("PrefsId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER");
b.Property<string>("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
}
}
}

View File

@@ -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<PreferencesDbContext> options) : base(options)
{
}
public DbSet<Prefs> Preferences { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Prefs>()
.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<HumanoidProfile> HumanoidProfiles { get; } = new List<HumanoidProfile>();
}
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!;
}
}

View File

@@ -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<PreferencesDbContext>();
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();
}
}
}

View File

@@ -12,12 +12,11 @@
</PropertyGroup>
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.30" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="3.1.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="YamlDotNet" Version="6.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj"/>
<ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj">
<Private>false</Private>
</ProjectReference>
@@ -32,7 +31,4 @@
</ProjectReference>
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Preferences\Migrations\000_Initial.sql" />
</ItemGroup>
</Project>

View File

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

View File

@@ -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
{
/// <summary>
/// Ensures database schemas are up to date.
/// </summary>
public static class MigrationManager
{
/// <summary>
/// Ensures the database schema for the given connection string is up to date.
/// </summary>
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);
}
}
}
/// <summary>
/// Generated for each SQL file found.
/// </summary>
private class Migration
{
public readonly string Id;
private readonly string _sql;
public Migration(string id, string sql)
{
Id = id;
_sql = sql;
}
/// <summary>
/// Executes the query in <see cref="_sql"/> and logs this in the SchemaVersion table.
/// </summary>
public void Run(IDbConnection connection)
{
connection.Execute(_sql);
InsertMigrationLog(connection, Id);
}
}
private const string InsertMigrationLogQuery =
@"INSERT INTO SchemaVersion (Id) VALUES (@Id)";
/// <summary>
/// Inserts a <see cref="MigrationLog"/> in the SchemaVersion table.
/// </summary>
private static void InsertMigrationLog(IDbConnection connection, string id)
{
Logger.InfoS("db", "Completing migration {0}", id);
connection.Execute(InsertMigrationLogQuery, new {Id = id});
}
/// <summary>
/// An entry in the SchemaVersion table.
/// </summary>
[UsedImplicitly]
private class MigrationLog
{
public string Id;
public string Timestamp;
}
private const string GetRanMigrationsQuery =
@"SELECT Id, Timestamp FROM SchemaVersion ORDER BY Id COLLATE NOCASE";
/// <summary>
/// Fetches a collection of <see cref="MigrationLog"/> from the SchemaVersion table and returns it.
/// </summary>
private static IEnumerable<MigrationLog> RanMigrations(IDbConnection connection)
{
return connection.Query<MigrationLog>(GetRanMigrationsQuery);
}
/// <summary>
/// Finds all available migrations, returns those that haven't been run yet.
/// </summary>
private static List<Migration> 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;
}
/// <summary>
/// Given an embedded resource's full path returns its contents as a string.
/// </summary>
private static string ResourceAssemblyToString(string resourceName)
{
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
/// <summary>
/// Searches the current assembly for SQL migration files.
/// TODO: Filter by subfolder so that different databases use different sets of migrations.
/// </summary>
[NotNull]
private static List<Migration> DiscoverMigrations(IDbConnection connection)
{
var results = new List<Migration>();
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;
}
/// <summary>
/// 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".
/// </summary>
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
)";
/// <summary>
/// Creates the SchemaVersion table if it doesn't exist.
/// </summary>
private static void EnsureSchemaVersionTableExists(IDbConnection connection)
{
connection.Execute(EnsureSchemaVersionTableExistsQuery);
}
}
}

View File

@@ -1,9 +1,7 @@
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;
@@ -15,156 +13,50 @@ namespace Content.Server.Preferences
/// </summary>
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<PlayerPreferencesSql>(
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())
foreach (var profile in prefs.HumanoidProfiles)
profiles[profile.Slot] = new HumanoidCharacterProfile
{
profiles[reader.GetInt32(0)] = new HumanoidCharacterProfile
{
Name = reader.GetString(1),
Age = reader.GetInt32(2),
Sex = reader.GetString(3) == "Male" ? Male : Female,
Name = profile.CharacterName,
Age = profile.Age,
Sex = profile.Sex == "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))
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.SelectedCharacterIndex,
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)
{
@@ -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,
CharacterName = humanoid.Name,
Age = humanoid.Age,
Sex = humanoid.Sex.ToString(),
HairStyleName = appearance.HairStyleName,
HairName = appearance.HairStyleName,
HairColor = appearance.HairColor.ToHex(),
FacialHairStyleName = appearance.FacialHairStyleName,
FacialHairName = appearance.FacialHairStyleName,
FacialHairColor = appearance.FacialHairColor.ToHex(),
EyeColor = appearance.EyeColor.ToHex(),
SkinColor = appearance.SkinColor.ToHex(),
Slot = slot,
Username = username
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);
}
}
}

View File

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

View File

@@ -21,5 +21,6 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lerp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Noto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=preemptively/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=prefs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=soundfonts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=swsl/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>