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