using System.Diagnostics; using SPTarkov.Common.Extensions; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Spt.Bots; using SPTarkov.Server.Core.Models.Spt.Server; using SPTarkov.Server.Core.Models.Spt.Templates; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using Hideout = SPTarkov.Server.Core.Models.Spt.Hideout.Hideout; using Locations = SPTarkov.Server.Core.Models.Spt.Server.Locations; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Services; /// /// Provides access to the servers database, these are in-memory representations of the .JSON files stored inside `Libraries\SPTarkov.Server.Assets\Assets\database` /// [Injectable(InjectionType.Singleton)] public class DatabaseService( ISptLogger logger, DatabaseServer databaseServer, ServerLocalisationService serverLocalisationService ) { private bool _isDataValid = true; /// assets/database/ public DatabaseTables GetTables() { return databaseServer.GetTables(); } /// assets/database/bots/ public Bots GetBots() { return databaseServer.GetTables().Bots; } /// assets/database/globals.json public Globals GetGlobals() { return databaseServer.GetTables().Globals; } /// assets/database/hideout/ public Hideout GetHideout() { return databaseServer.GetTables().Hideout; } /// assets/database/locales/ public LocaleBase GetLocales() { return databaseServer.GetTables().Locales; } /// assets/database/locations public Locations GetLocations() { return databaseServer.GetTables().Locations; } /// /// Get specific location by its ID, automatically ToLowers id /// /// Desired location ID /// assets/database/locations/ public Location? GetLocation(string locationId) { var desiredLocation = GetLocations().GetByJsonProp(locationId.ToLowerInvariant()); if (desiredLocation == null) { logger.Error(serverLocalisationService.GetText("database-no_location_found_with_id", locationId)); return null; } return desiredLocation; } /// assets/database/match/ public Match GetMatch() { return databaseServer.GetTables().Match; } /// assets/database/server.json public ServerBase GetServer() { return databaseServer.GetTables().Server; } /// assets/database/settings.json public SettingsBase GetSettings() { return databaseServer.GetTables().Settings; } /// assets/database/templates/ public Templates GetTemplates() { return databaseServer.GetTables().Templates; } /// assets/database/templates/achievements.json public List GetAchievements() { return databaseServer.GetTables().Templates.Achievements; } /// assets/database/templates/customAchievements.json public List GetCustomAchievements() { return databaseServer.GetTables().Templates.CustomAchievements; } /// assets/database/templates/customisation.json public Dictionary GetCustomization() { return databaseServer.GetTables().Templates.Customization; } /// assets/database/templates/handbook.json public HandbookBase GetHandbook() { return databaseServer.GetTables().Templates.Handbook; } /// assets/database/templates/items.json public Dictionary GetItems() { return databaseServer.GetTables().Templates.Items; } /// assets/database/templates/prices.json public Dictionary GetPrices() { return databaseServer.GetTables().Templates.Prices; } /// assets/database/templates/profiles.json public Dictionary GetProfileTemplates() { return databaseServer.GetTables().Templates.Profiles; } /// assets/database/templates/quests.json public Dictionary GetQuests() { return databaseServer.GetTables().Templates.Quests; } /// assets/database/traders/ public Dictionary GetTraders() { return databaseServer.GetTables().Traders; } /// /// Get specific trader by their ID /// /// Desired trader ID /// assets/database/traders/ public Trader? GetTrader(MongoId traderId) { if (!databaseServer.GetTables().Traders.TryGetValue(traderId, out var desiredTrader)) { return null; } return desiredTrader; } /// assets/database/locationServices/ public LocationServices GetLocationServices() { if (databaseServer.GetTables().Templates?.LocationServices == null) { throw new Exception( serverLocalisationService.GetText("database-data_at_path_missing", "assets/database/locationServices.json") ); } return databaseServer.GetTables().Templates?.LocationServices!; } /// /// Validates that the database doesn't contain invalid ID data /// public void ValidateDatabase() { var start = Stopwatch.StartNew(); _isDataValid = ValidateTable(GetQuests(), "quest") && ValidateTable(GetTraders(), "trader") && ValidateTable(GetItems(), "item") && ValidateTable(GetCustomization(), "customization"); if (!_isDataValid) { logger.Error(serverLocalisationService.GetText("database-invalid_data")); } start.Stop(); if (logger.IsLogEnabled(LogLevel.Debug)) { logger.Debug($"ID validation took: {start.ElapsedMilliseconds}ms"); } } /// /// Validate that the given table only contains valid MongoIDs /// /// Table to validate for MongoIDs /// The type of table, used in output message /// True if the table only contains valid data private bool ValidateTable(Dictionary table, string tableType) { foreach (var keyValuePair in table) { if (!keyValuePair.Key.IsValidMongoId()) { logger.Error($"Invalid {tableType} ID: '{keyValuePair.Key}'"); return false; } } return true; } private bool ValidateTable(Dictionary table, string tableType) { foreach (var keyValuePair in table) { if (!keyValuePair.Key.IsValidMongoId()) { logger.Error($"Invalid {tableType} ID: '{keyValuePair.Key}'"); return false; } } return true; } /// /// Check if the database is valid /// /// True if the database contains valid data, false otherwise public bool IsDatabaseValid() { return _isDataValid; } }