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 ) { /// /// Key = Item tpl, values = Ids of its parents /// private Dictionary> _itemBaseClassesCache = []; private readonly Lock _itemBaseClassesLock = new(); 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 = []; var items = databaseService.GetItems(); foreach (var item in items) { AddItemToCache(item.Key); } } public void AddItemToCache(MongoId itemTpl) { var itemDb = databaseService.GetItems(); if (!itemDb.TryGetValue(itemTpl, out var item)) { logger.Error($"Could not add {itemTpl} to cache, it does not exist in the item database!"); return; } lock (_itemBaseClassesLock) { if (string.Equals(item.Type, "Item", StringComparison.OrdinalIgnoreCase)) { _itemBaseClassesCache.TryAdd(item.Id, []); AddBaseItems(item.Id, item); } else { _rootNodeIds.Add(item.Id); } } } /// /// 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 (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 in cache, attempt to add first AddItemToCache(itemTpl); existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out baseClassList); } if (existsInCache) { return baseClassList.Overlaps(baseClasses); } logger.Warning(serverLocalisationService.GetText("baseclass-item_not_found_failed", itemTpl.ToString())); 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 (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 in cache, attempt to add first AddItemToCache(itemTpl); existsInCache = _itemBaseClassesCache.TryGetValue(itemTpl, out baseClassList); } if (existsInCache) { return baseClassList.Contains(baseClasses); } logger.Warning(serverLocalisationService.GetText("baseclass-item_not_found_failed", itemTpl.ToString())); 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 (!_itemBaseClassesCache.TryGetValue(itemTpl, out var value)) { return []; } return value; } }