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;
}
}