using System.Diagnostics; using SPTarkov.Common.Extensions; using SPTarkov.DI.Annotations; 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 SPTarkov.Server.Core.Utils; 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, LocalisationService _localisationService, HashUtil _hashUtil ) { protected bool isDataValid = true; /// assets/database/ public DatabaseTables GetTables() { return _databaseServer.GetTables(); } /// assets/database/bots/ public Bots GetBots() { if (_databaseServer.GetTables().Bots == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/bots")); } return _databaseServer.GetTables().Bots!; } /// assets/database/globals.json public Globals GetGlobals() { if (_databaseServer.GetTables().Globals == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/globals.json" ) ); } return _databaseServer.GetTables().Globals!; } /// assets/database/hideout/ public Hideout GetHideout() { if (_databaseServer.GetTables().Hideout == null) { throw new Exception( _localisationService.GetText("database-data_at_path_missing", "assets/database/hideout") ); } return _databaseServer.GetTables().Hideout!; } /// assets/database/locales/ public LocaleBase GetLocales() { if (_databaseServer.GetTables().Locales == null) { throw new Exception( _localisationService.GetText("database-data_at_path_missing", "assets/database/locales") ); } return _databaseServer.GetTables().Locales!; } /// assets/database/locations public Locations GetLocations() { if (_databaseServer.GetTables().Locations == null) { throw new Exception( _localisationService.GetText("database-data_at_path_missing", "assets/database/locations") ); } 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 locations = GetLocations(); var desiredLocation = locations.GetByJsonProp(locationId.ToLower()); if (desiredLocation == null) { throw new Exception(_localisationService.GetText("database-no_location_found_with_id", locationId)); } return desiredLocation; } /// assets/database/match/ public Match GetMatch() { if (_databaseServer.GetTables().Match == null) { throw new Exception( _localisationService.GetText("database-data_at_path_missing", "assets/database/locales") ); } return _databaseServer.GetTables().Match!; } /// assets/database/server.json public ServerBase GetServer() { if (_databaseServer.GetTables().Server == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/server.json" ) ); } return _databaseServer.GetTables().Server!; } /// assets/database/settings.json public SettingsBase GetSettings() { if (_databaseServer.GetTables().Settings == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/settings.json" ) ); } return _databaseServer.GetTables().Settings!; } /// assets/database/templates/ public Templates GetTemplates() { if (_databaseServer.GetTables().Templates == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/templates" ) ); } return _databaseServer.GetTables().Templates!; } /// assets/database/templates/achievements.json public List GetAchievements() { if (_databaseServer.GetTables().Templates?.Achievements == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/templates/achievements.json" ) ); } return _databaseServer.GetTables().Templates?.Achievements!; } /// assets/database/templates/customAchievements.json public List GetCustomAchievements() { if (_databaseServer.GetTables().Templates?.Achievements == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/templates/customAchievements.json" ) ); } return _databaseServer.GetTables().Templates?.CustomAchievements!; } /// assets/database/templates/customisation.json public Dictionary GetCustomization() { if (_databaseServer.GetTables().Templates?.Customization == null) { throw new Exception( _localisationService.GetText( "database-data_at_path_missing", "assets/database/templates/customization.json" ) ); } return _databaseServer.GetTables().Templates?.Customization!; } /// assets/database/templates/handbook.json public HandbookBase GetHandbook() { if (_databaseServer.GetTables().Templates?.Handbook == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/templates/handbook.json")); } return _databaseServer.GetTables().Templates?.Handbook!; } /// assets/database/templates/items.json public Dictionary GetItems() { if (_databaseServer.GetTables().Templates?.Items == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/templates/items.json")); } return _databaseServer.GetTables().Templates?.Items!; } /// assets/database/templates/prices.json public Dictionary GetPrices() { if (_databaseServer.GetTables().Templates?.Prices == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/templates/prices.json")); } return _databaseServer.GetTables().Templates?.Prices!; } /// assets/database/templates/profiles.json public Dictionary GetProfileTemplates() { if (_databaseServer.GetTables().Templates?.Profiles == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/templates/profiles.json")); } return _databaseServer.GetTables().Templates?.Profiles!; } /// assets/database/templates/quests.json public Dictionary GetQuests() { if (_databaseServer.GetTables().Templates?.Quests == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/templates/quests.json")); } return _databaseServer.GetTables().Templates?.Quests!; } /// assets/database/traders/ public Dictionary GetTraders() { if (_databaseServer.GetTables().Traders == null) { throw new Exception(_localisationService.GetText("database-data_at_path_missing", "assets/database/traders")); } return _databaseServer.GetTables().Traders!; } /// /// Get specific trader by their ID /// /// Desired trader ID /// assets/database/traders/ public Trader GetTrader(string traderId) { var traders = GetTraders(); if (!traders.TryGetValue(traderId, out var desiredTrader)) { throw new Exception(_localisationService.GetText("database-no_trader_found_with_id", traderId)); } return desiredTrader; } /// assets/database/locationServices/ public LocationServices GetLocationServices() { if (_databaseServer.GetTables().Templates?.LocationServices == null) { throw new Exception(_localisationService.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(_localisationService.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 (!_hashUtil.IsValidMongoId(keyValuePair.Key)) { _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; } }