using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Utils; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Services; /// /// Cache the baseids for each item in the items db inside a dictionary /// [Injectable(InjectionType.Singleton)] public class ItemBaseClassService( ISptLogger logger, DatabaseService databaseService, ServerLocalisationService serverLocalisationService ) { private bool _cacheGenerated; /// /// Key = Item tpl, values = Ids of its parents /// private Dictionary> _itemBaseClassesCache = []; private readonly HashSet _rootNodeIds = []; /// /// Create cache and store inside ItemBaseClassService
/// Store a dict of an items tpl to the base classes it and its parents have ///
public void HydrateItemBaseClassCache() { // Clear existing cache _itemBaseClassesCache = new Dictionary>(); var items = databaseService.GetItems(); foreach (var item in items) { if (string.Equals(item.Value.Type, "Item", StringComparison.OrdinalIgnoreCase)) { var itemIdToUpdate = item.Value.Id; if (!_itemBaseClassesCache.ContainsKey(item.Value.Id)) { _itemBaseClassesCache[item.Value.Id] = []; } AddBaseItems(itemIdToUpdate, item.Value); } else { _rootNodeIds.Add(item.Key); } } _cacheGenerated = true; } /// /// Helper method, recursively iterate through items parent items, finding and adding ids to dictionary /// /// Item tpl to store base ids against in dictionary /// Item being checked protected void AddBaseItems(MongoId itemIdToUpdate, TemplateItem item) { _itemBaseClassesCache[itemIdToUpdate].Add(item.Parent); databaseService.GetItems().TryGetValue(item.Parent, out var parent); if (parent is not null && !parent.Parent.IsEmpty()) { AddBaseItems(itemIdToUpdate, parent); } } /// /// Does item tpl inherit from the requested base class /// /// ItemTpl item to check base classes of /// BaseClass base class to check for /// true if item inherits from base class passed in public bool ItemHasBaseClass(MongoId itemTpl, IEnumerable baseClasses) { if (!_cacheGenerated) { HydrateItemBaseClassCache(); } if (itemTpl.IsEmpty()) { logger.Warning("Unable to check itemTpl base class as value passed is null"); return false; } // The cache is only generated for item templates with `_type == "Item"`, so return false for any other type, // including item templates that simply don't exist. if (_rootNodeIds.Contains(itemTpl)) { return false; } var existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out var baseClassList); if (!existsInCache) { // Not found if (logger.IsLogEnabled(LogLevel.Debug)) { logger.Debug(serverLocalisationService.GetText("baseclass-item_not_found", itemTpl)); } // Not found in cache, Hydrate again - some mods add items late in server startup lifecycle HydrateItemBaseClassCache(); existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out baseClassList); } if (existsInCache) { return baseClassList.Overlaps(baseClasses); } logger.Warning(serverLocalisationService.GetText("baseclass-item_not_found_failed", itemTpl)); return false; } /// /// Does item tpl inherit from the requested base class /// /// ItemTpl item to check base classes of /// BaseClass base class to check for /// true if item inherits from base class passed in public bool ItemHasBaseClass(MongoId itemTpl, MongoId baseClasses) { if (!_cacheGenerated) { HydrateItemBaseClassCache(); } if (itemTpl.IsEmpty()) { logger.Warning("Unable to check itemTpl base class as value passed is null"); return false; } // The cache is only generated for item templates with `_type == "Item"`, so return false for any other type, // including item templates that simply don't exist. if (_rootNodeIds.Contains(itemTpl)) { return false; } var existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out var baseClassList); if (!existsInCache) { // Not found if (logger.IsLogEnabled(LogLevel.Debug)) { logger.Debug(serverLocalisationService.GetText("baseclass-item_not_found", itemTpl)); } // Not found in cache, Hydrate again - some mods add items late in server startup lifecycle HydrateItemBaseClassCache(); existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out baseClassList); } if (existsInCache) { return baseClassList.Contains(baseClasses); } logger.Warning(serverLocalisationService.GetText("baseclass-item_not_found_failed", itemTpl)); return false; } /// /// Get base classes item inherits from /// /// ItemTpl item to get base classes for /// array of base classes public HashSet GetItemBaseClasses(MongoId itemTpl) { if (!_cacheGenerated) { HydrateItemBaseClassCache(); } if (!_itemBaseClassesCache.TryGetValue(itemTpl, out var value)) { return []; } return value; } }