using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; using Core.Models.Spt.Config; using Core.Servers; using Core.Services; using Core.Utils.Cloners; namespace Core.Helpers; [Injectable(InjectionType.Singleton)] public class HandbookHelper( DatabaseService _databaseService, ConfigServer _configServer, ICloner _cloner ) { protected ItemConfig _itemConfig = _configServer.GetConfig(); protected bool _lookupCacheGenerated = false; protected LookupCollection _handbookPriceCache = new(); /// /// Create an in-memory cache of all items with associated handbook price in handbookPriceCache class /// public void HydrateLookup() { var handbook = _databaseService.GetHandbook(); // Add handbook overrides found in items.json config into db foreach (var itemTplKey in _itemConfig.HandbookPriceOverride) { var data = _itemConfig.HandbookPriceOverride[itemTplKey.Key]; var itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == itemTplKey.Key); if (itemToUpdate is null) { handbook.Items.Add( new HandbookItem { Id = itemTplKey.Key, ParentId = data.ParentId, Price = data.Price } ); itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == itemTplKey.Key); } itemToUpdate.Price = data.Price; } var handbookDbClone = _cloner.Clone(handbook); foreach (var handbookItem in handbookDbClone.Items) { _handbookPriceCache.Items.ById.TryAdd(handbookItem.Id, handbookItem.Price ?? 0); if (!_handbookPriceCache.Items.ByParent.TryGetValue(handbookItem.ParentId, out _)) _handbookPriceCache.Items.ByParent.TryAdd(handbookItem.ParentId, []); _handbookPriceCache.Items.ByParent.TryGetValue(handbookItem.ParentId, out var array); array.Add(handbookItem.Id); } foreach (var handbookCategory in handbookDbClone.Categories) { _handbookPriceCache.Categories.ById.TryAdd(handbookCategory.Id, handbookCategory.ParentId); if (handbookCategory.ParentId is not null) { if (!_handbookPriceCache.Categories.ByParent.TryGetValue(handbookCategory.ParentId, out _)) _handbookPriceCache.Categories.ByParent.TryAdd(handbookCategory.ParentId, []); _handbookPriceCache.Categories.ByParent.TryGetValue(handbookCategory.ParentId, out var array); array.Add(handbookCategory.Id); } } } /// /// Get price from internal cache, if cache empty look up price directly in handbook (expensive) /// If no values found, return 0 /// /// Item tpl to look up price for /// price in roubles public double GetTemplatePrice(string tpl) { if (!_lookupCacheGenerated) { HydrateLookup(); _lookupCacheGenerated = true; } if (_handbookPriceCache.Items.ById.TryGetValue(tpl, out var item)) return item; var handbookItem = _databaseService.GetHandbook().Items?.FirstOrDefault(item => item.Id == tpl); if (handbookItem is null) { var newValue = 0; if (!_handbookPriceCache.Items.ById.TryAdd(tpl, newValue)) _handbookPriceCache.Items.ById[tpl] = newValue; return newValue; } if (!_handbookPriceCache.Items.ById.TryAdd(tpl, handbookItem.Price ?? 0)) _handbookPriceCache.Items.ById[tpl] = handbookItem.Price ?? 0; return handbookItem.Price.Value; } public double GetTemplatePriceForItems(List items) { var total = 0D; foreach (var item in items) total += GetTemplatePrice(item.Template); return total; } /// /// Get all items in template with the given parent category /// /// /// string array public List TemplatesWithParent(string parentId) { _handbookPriceCache.Items.ByParent.TryGetValue(parentId, out var template); return template ?? []; } /// /// Does category exist in handbook cache /// /// /// true if exists in cache public bool IsCategory(string category) { return _handbookPriceCache.Categories.ById.TryGetValue(category, out _); } /// /// Get all items associated with a categories parent /// /// /// string array public List ChildrenCategories(string categoryParent) { _handbookPriceCache.Categories.ByParent.TryGetValue(categoryParent, out var category); return category ?? []; } /// /// Convert non-roubles into roubles /// /// Currency count to convert /// What current currency is /// Count in roubles public int InRUB(double nonRoubleCurrencyCount, string currencyTypeFrom) { return (int)(currencyTypeFrom == Money.ROUBLES ? nonRoubleCurrencyCount : Math.Round(nonRoubleCurrencyCount * GetTemplatePrice(currencyTypeFrom))); } /// /// Convert roubles into another currency /// /// roubles to convert /// Currency to convert roubles into /// currency count in desired type public int FromRUB(double roubleCurrencyCount, string currencyTypeTo) { if (currencyTypeTo == Money.ROUBLES) return (int)roubleCurrencyCount; // Get price of currency from handbook var price = GetTemplatePrice(currencyTypeTo); return (int)(price > 0 ? Math.Max(1, Math.Round(roubleCurrencyCount / price)) : 0); } public HandbookCategory GetCategoryById(string handbookId) { return _databaseService.GetHandbook().Categories.FirstOrDefault(category => category.Id == handbookId); } } public class LookupItem { public Dictionary ById { get; set; } public Dictionary> ByParent { get; set; } public LookupItem() { ById = new Dictionary(); ByParent = new Dictionary>(); } } public class LookupCollection { public LookupItem Items { get; set; } public LookupItem Categories { get; set; } public LookupCollection() { Items = new LookupItem(); Categories = new LookupItem(); } }