using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Content.Server.Database; using Content.Server.Interfaces; using Content.Shared.Preferences; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Resources; using Robust.Shared.IoC; namespace Content.Server.Preferences { /// /// Sends before the client joins the lobby. /// Receives and at any time. /// public class ServerPreferencesManager : SharedPreferencesManager, IServerPreferencesManager { #pragma warning disable 649 [Dependency] private readonly IServerNetManager _netManager; [Dependency] private readonly IConfigurationManager _configuration; [Dependency] private readonly IResourceManager _resourceManager; #pragma warning restore 649 private PreferencesDatabase _preferencesDb; private Task _prefsDbLoadTask; public void StartInit() { _netManager.RegisterNetMessage(nameof(MsgPreferencesAndSettings)); _netManager.RegisterNetMessage(nameof(MsgSelectCharacter), HandleSelectCharacterMessage); _netManager.RegisterNetMessage(nameof(MsgUpdateCharacter), HandleUpdateCharacterMessage); _configuration.RegisterCVar("game.maxcharacterslots", 10); _configuration.RegisterCVar("database.prefs_engine", "sqlite"); _configuration.RegisterCVar("database.prefs_sqlite_dbpath", "preferences.db"); _configuration.RegisterCVar("database.prefs_pg_host", "localhost"); _configuration.RegisterCVar("database.prefs_pg_port", 5432); _configuration.RegisterCVar("database.prefs_pg_database", "ss14_prefs"); _configuration.RegisterCVar("database.prefs_pg_username", string.Empty); _configuration.RegisterCVar("database.prefs_pg_password", string.Empty); var engine = _configuration.GetCVar("database.prefs_engine").ToLower(); IDatabaseConfiguration dbConfig; switch (engine) { case "sqlite": var configPreferencesDbPath = _configuration.GetCVar("database.prefs_sqlite_dbpath"); var inMemory = _resourceManager.UserData.RootDir == null; var finalPreferencesDbPath = inMemory ? null : Path.Combine(_resourceManager.UserData.RootDir, configPreferencesDbPath); dbConfig = new SqliteConfiguration(finalPreferencesDbPath); break; case "postgres": dbConfig = new PostgresConfiguration( _configuration.GetCVar("database.prefs_pg_host"), _configuration.GetCVar("database.prefs_pg_port"), _configuration.GetCVar("database.prefs_pg_database"), _configuration.GetCVar("database.prefs_pg_username"), _configuration.GetCVar("database.prefs_pg_password") ); break; default: throw new NotImplementedException("Unknown database engine {engine}."); } var maxCharacterSlots = _configuration.GetCVar("game.maxcharacterslots"); // Actually loading the preferences database takes a while, // because EFCore has to initialize and run migrations. // We load it in the thread pool here and then fetch the .Result in FinishInit. // This means it'll run in parallel with other loading like prototypes & map load. _prefsDbLoadTask = Task.Run(() => new PreferencesDatabase(dbConfig, maxCharacterSlots)); } public void FinishInit() { _preferencesDb = _prefsDbLoadTask.Result; } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) { await _preferencesDb.SaveSelectedCharacterIndexAsync(message.MsgChannel.SessionId.Username, message.SelectedCharacterIndex); } private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) { await _preferencesDb.SaveCharacterSlotAsync(message.MsgChannel.SessionId.Username, message.Profile, message.Slot); } public async void OnClientConnected(IPlayerSession session) { var msg = _netManager.CreateNetMessage(); msg.Preferences = await GetPreferencesAsync(session.SessionId.Username); msg.Settings = new GameSettings { MaxCharacterSlots = _configuration.GetCVar("game.maxcharacterslots") }; _netManager.ServerSendMessage(msg, session.ConnectedClient); } /// /// Returns the requested or null if not found. /// private async Task GetFromSql(string username) { return await _preferencesDb.GetPlayerPreferencesAsync(username); } /// /// Retrieves preferences for the given username from storage. /// Creates and saves default preferences if they are not found, then returns them. /// public async Task GetPreferencesAsync(string username) { var prefs = await GetFromSql(username); if (prefs is null) { await _preferencesDb.SaveSelectedCharacterIndexAsync(username, 0); await _preferencesDb.SaveCharacterSlotAsync(username, HumanoidCharacterProfile.Default(), 0); prefs = await GetFromSql(username); } return prefs; } public async Task>> GetSelectedProfilesForPlayersAsync(List usernames) { return await _preferencesDb.GetSelectedProfilesForPlayersAsync(usernames); } } }