using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Utils; namespace SPTarkov.Server.Core.Services; [Injectable(InjectionType.Singleton)] public class RagfairLinkedItemService(DatabaseService databaseService, ItemHelper itemHelper, ISptLogger logger) { protected readonly Dictionary> linkedItemsCache = new(); public HashSet GetLinkedItems(MongoId linkedSearchId) { if (!linkedItemsCache.TryGetValue(linkedSearchId, out var set)) { // Regenerate cache BuildLinkedItemTable(); return linkedItemsCache[linkedSearchId]; } return set; } /// /// Use ragfair linked item service to get a list of items that can fit on or in designated itemTpl /// /// Item to get sub-items for /// TemplateItem list public List GetLinkedDbItems(MongoId itemTpl) { var linkedItemsToWeaponTpls = GetLinkedItems(itemTpl); return linkedItemsToWeaponTpls.Aggregate( new List(), (result, linkedTpl) => { var itemDetails = itemHelper.GetItem(linkedTpl); if (itemDetails.Key) { result.Add(itemDetails.Value); } else { logger.Warning($"Item {itemTpl} has invalid linked item {linkedTpl}"); } return result; } ); } /// /// Create Dictionary of every item and the items associated with it /// protected void BuildLinkedItemTable() { var linkedItems = new Dictionary>(); foreach (var item in databaseService.GetItems().Values) { // Ensure hashset exists for item linkedItems.TryAdd(item.Id, []); var itemLinkedSet = linkedItems[item.Id]; // Slots foreach (var linkedItemId in GetSlotFilters(item)) { itemLinkedSet.Add(linkedItemId); linkedItems.TryAdd(linkedItemId, []); linkedItems[linkedItemId].Add(item.Id); } // Chambers foreach (var linkedItemId in GetChamberFilters(item)) { itemLinkedSet.Add(linkedItemId); linkedItems.TryAdd(linkedItemId, []); linkedItems[linkedItemId].Add(item.Id); } // Cartridges foreach (var linkedItemId in GetCartridgeFilters(item)) { itemLinkedSet.Add(linkedItemId); linkedItems.TryAdd(linkedItemId, []); linkedItems[linkedItemId].Add(item.Id); } // Edge case, ensure ammo for revolvers is included if (item.Parent == BaseClasses.REVOLVER) // Find magazine for revolver { AddRevolverCylinderAmmoToLinkedItems(item, itemLinkedSet); } } // We have our linked item pool generated, add to class property foreach (var item in linkedItems) { linkedItemsCache.Add(item.Key, item.Value); } } /// /// Add ammo to revolvers linked item dictionary /// /// Revolvers cylinder /// Set to add to protected void AddRevolverCylinderAmmoToLinkedItems(TemplateItem cylinder, HashSet itemLinkedSet) { var cylinderMod = cylinder.Properties.Slots?.FirstOrDefault(x => x.Name == "mod_magazine"); if (cylinderMod == null) { return; } // Get the first cylinder filter tpl var cylinderTpl = cylinderMod.Props?.Filters?.First().Filter?.FirstOrDefault() ?? new MongoId(null); if (!cylinderTpl.IsValidMongoId()) { // No cylinder, nothing to do return; } // Get db data for cylinder tpl, add found slots info (camora_xxx) to linked items on revolver weapon var cylinderTemplate = itemHelper.GetItem(cylinderTpl).Value; itemLinkedSet.UnionWith(GetSlotFilters(cylinderTemplate)); } /// /// Get a set of unique tpls from an items Slot 'filter' array /// /// Db item to get tpls from /// Set of tpls protected HashSet GetSlotFilters(TemplateItem item) { var result = new HashSet(); var slots = item.Properties?.Slots; if (slots is null || !slots.Any()) { // No slots, skip return result; } // Check each slot and merge contents together into result set foreach (var slot in slots) { if (slot.Props?.Filters is null) { continue; } foreach (var slotFilters in slot.Props.Filters) { result.UnionWith(slotFilters.Filter); } } return result; } protected HashSet GetChamberFilters(TemplateItem item) { var result = new HashSet(); var chambers = item.Properties?.Chambers; if (chambers is null || !chambers.Any()) { return result; } foreach (var chamber in chambers) { if (chamber.Props?.Filters is null) { continue; } foreach (var slotFilters in chamber.Props.Filters) { result.UnionWith(slotFilters.Filter); } } return result; } protected HashSet GetCartridgeFilters(TemplateItem item) { var result = new HashSet(); var cartridges = item.Properties?.Cartridges; if (cartridges is null || !cartridges.Any()) { return result; } foreach (var cartridge in cartridges) { if (cartridge.Props?.Filters is null) { continue; } foreach (var slotFilters in cartridge.Props.Filters) { result.UnionWith(slotFilters.Filter); } } return result; } }