using SPTarkov.Common.Annotations; 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, LocalisationService _localisationService ) { private bool _cacheGenerated; private Dictionary> _itemBaseClassesCache; /// /// 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(); var filteredDbItems = items.Where(x => string.Equals(x.Value.Type, "Item", StringComparison.OrdinalIgnoreCase)); foreach (var item in filteredDbItems) { var itemIdToUpdate = item.Value.Id; if (!_itemBaseClassesCache.ContainsKey(item.Value.Id)) { _itemBaseClassesCache[item.Value.Id] = []; } AddBaseItems(itemIdToUpdate, item.Value); } _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(string itemIdToUpdate, TemplateItem item) { _itemBaseClassesCache[itemIdToUpdate].Add(item.Parent); var parent = _databaseService.GetItems()[item.Parent]; if (!string.IsNullOrEmpty(parent.Parent)) { 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(string itemTpl, ICollection baseClasses) { if (!_cacheGenerated) { HydrateItemBaseClassCache(); } if (string.IsNullOrEmpty(itemTpl)) { _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 (!CachedItemIsOfItemType(itemTpl)) { return false; } if (_itemBaseClassesCache.TryGetValue(itemTpl, out var baseClassList)) { return baseClassList.Overlaps(baseClasses); } if (_logger.IsLogEnabled(LogLevel.Debug)) { _logger.Debug(_localisationService.GetText("baseclass-item_not_found", itemTpl)); } // Not found in cache, Hydrate again - some mods add items late HydrateItemBaseClassCache(); // Check for item again, return false if item not found a second time if (_itemBaseClassesCache.TryGetValue(itemTpl, out var value)) { return value.Any(baseClasses.Contains); } _logger.Warning(_localisationService.GetText("baseclass-item_not_found_failed", itemTpl)); return false; } /// /// Check if cached item template is of type Item /// /// ItemTemplateId item to check /// True if item is of type Item private bool CachedItemIsOfItemType(string itemTemplateId) { return string.Equals(_databaseService.GetItems()[itemTemplateId]?.Type, "Item", StringComparison.OrdinalIgnoreCase); } /// /// Get base classes item inherits from /// /// ItemTpl item to get base classes for /// array of base classes public List GetItemBaseClasses(string itemTpl) { if (!_cacheGenerated) { HydrateItemBaseClassCache(); } if (!_itemBaseClassesCache.ContainsKey(itemTpl)) { return []; } return _itemBaseClassesCache[itemTpl].ToList(); } }