diff --git a/Libraries/Core/Helpers/RagfairHelper.cs b/Libraries/Core/Helpers/RagfairHelper.cs index 8c49d730..df0c54fd 100644 --- a/Libraries/Core/Helpers/RagfairHelper.cs +++ b/Libraries/Core/Helpers/RagfairHelper.cs @@ -1,54 +1,166 @@ using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.Ragfair; +using Core.Models.Enums; +using Core.Models.Spt.Config; +using Core.Models.Utils; +using Core.Servers; +using Core.Services; +using Core.Utils.Cloners; namespace Core.Helpers; [Injectable] -public class RagfairHelper +public class RagfairHelper( + TraderAssortHelper traderAssortHelper, + DatabaseService databaseService, + HandbookHelper handbookHelper, + ItemHelper itemHelper, + RagfairLinkedItemService ragfairLinkedItemService, + UtilityHelper utilityHelper, + ConfigServer configServer, + ICloner cloner +) { - /// - /// Gets currency TAG from TPL - /// - /// currency - /// string + protected RagfairConfig ragfairConfig = configServer.GetConfig(); + + /** + * Gets currency TAG from TPL + * @param {string} currency + * @returns string + */ public string GetCurrencyTag(string currency) { - throw new NotImplementedException(); + switch (currency) { + case Money.EUROS: + return "EUR"; + case Money.DOLLARS: + return "USD"; + case Money.ROUBLES: + return "RUB"; + case Money.GP: + return "GP"; + default: + return ""; + } } public List FilterCategories(string sessionID, SearchRequestData request) { - throw new NotImplementedException(); + var result = new List(); + + // Case: weapon builds + if (request.BuildCount != null) { + return request.BuildItems.Keys.ToList(); + } + + // Case: search + if (request.LinkedSearchId != null) { + var data = ragfairLinkedItemService.GetLinkedItems(request.LinkedSearchId); + result = data == null ? [] : [..data]; + } + + // Case: category + if (request.HandbookId != null) { + var handbook = GetCategoryList(request.HandbookId); + + if (result.Count != null && result.Count > 0) { + result = utilityHelper.ArrayIntersect(result, handbook); + } else { + result = handbook; + } + } + + return result; } public Dictionary GetDisplayableAssorts(string sessionID) { - throw new NotImplementedException(); + var result = new Dictionary(); + + foreach (var traderID in databaseService.GetTraders().Keys) { + if (ragfairConfig.Traders.ContainsKey(traderID)) { + result[traderID] = traderAssortHelper.GetAssort(sessionID, traderID, true); + } + } + + return result; } protected List GetCategoryList(string handbookId) { - throw new NotImplementedException(); + var result = new List(); + + // if its "mods" great-parent category, do double recursive loop + if (handbookId == "5b5f71a686f77447ed5636ab") { + foreach (var categ in handbookHelper.ChildrenCategories(handbookId)) { + foreach (var subcateg in handbookHelper.ChildrenCategories(categ)) { + result = [..result, ..handbookHelper.TemplatesWithParent(subcateg)]; + } + } + + return result; + } + + // item is in any other category + if (handbookHelper.IsCategory(handbookId)) { + // list all item of the category + result = handbookHelper.TemplatesWithParent(handbookId); + + foreach (var categ in handbookHelper.ChildrenCategories(handbookId)) { + result = [..result, ..handbookHelper.TemplatesWithParent(categ)]; + } + + return result; + } + + // its a specific item searched + result.Add(handbookId); + return result; } - /// - /// Iterate over array of identical items and merge stack count - /// Ragfair allows abnormally large stacks. - /// + /** + * Iterate over array of identical items and merge stack count + * Ragfair allows abnormally large stacks. + */ public List MergeStackable(List items) { - throw new NotImplementedException(); + var list = new List(); + Item rootItem = null; + + foreach (var item in items) { + var itemFixed = itemHelper.FixItemStackCount(item); + + var isChild = items.Any(it => it.Id == itemFixed.ParentId); + if (!isChild) { + if (rootItem == null) { + rootItem = cloner.Clone(itemFixed); + rootItem.Upd.OriginalStackObjectsCount = rootItem.Upd.StackObjectsCount; + } else { + rootItem.Upd.StackObjectsCount += itemFixed.Upd.StackObjectsCount; + list.Add(itemFixed); + } + } else { + list.Add(itemFixed); + } + } + + return [rootItem, ..list]; } - /// - /// Return the symbol for a currency - /// e.g. 5449016a4bdc2d6f028b456f return ₽ - /// - /// currency to get symbol for - /// symbol of currency + /** + * Return the symbol for a currency + * e.g. 5449016a4bdc2d6f028b456f return ₽ + * @param currencyTpl currency to get symbol for + * @returns symbol of currency + */ public string GetCurrencySymbol(string currencyTpl) { - throw new NotImplementedException(); + return currencyTpl switch + { + Money.EUROS => "€", + Money.DOLLARS => "$", + _ => "₽" + }; } } diff --git a/Libraries/Core/Models/Eft/Common/Tables/Item.cs b/Libraries/Core/Models/Eft/Common/Tables/Item.cs index 2a374f05..b15df3c0 100644 --- a/Libraries/Core/Models/Eft/Common/Tables/Item.cs +++ b/Libraries/Core/Models/Eft/Common/Tables/Item.cs @@ -49,7 +49,7 @@ public record ItemLocation public record Upd { public UpdBuff? Buff { get; set; } - public int? OriginalStackObjectsCount { get; set; } + public double? OriginalStackObjectsCount { get; set; } public UpdTogglable? Togglable { get; set; } public UpdMap? Map { get; set; } public UpdTag? Tag { get; set; } diff --git a/Libraries/Core/Models/Eft/Ragfair/SearchRequestData.cs b/Libraries/Core/Models/Eft/Ragfair/SearchRequestData.cs index 9a590696..05698e9f 100644 --- a/Libraries/Core/Models/Eft/Ragfair/SearchRequestData.cs +++ b/Libraries/Core/Models/Eft/Ragfair/SearchRequestData.cs @@ -64,7 +64,7 @@ public record SearchRequestData : IRequestData public string? NeededSearchId { get; set; } [JsonPropertyName("buildItems")] - public BuildItems? BuildItems { get; set; } + public Dictionary? BuildItems { get; set; } [JsonPropertyName("buildCount")] public int? BuildCount { get; set; } @@ -82,8 +82,3 @@ public enum OfferOwnerType TRADEROWNERTYPE = 1, PLAYEROWNERTYPE = 2 } - -public record BuildItems -{ - // Define properties for BuildItems here if needed -} diff --git a/Libraries/Core/Services/RagfairLinkedItemService.cs b/Libraries/Core/Services/RagfairLinkedItemService.cs index 5a76487d..3eb7d433 100644 --- a/Libraries/Core/Services/RagfairLinkedItemService.cs +++ b/Libraries/Core/Services/RagfairLinkedItemService.cs @@ -1,54 +1,137 @@ -using SptCommon.Annotations; +using Core.Helpers; +using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; +using Core.Models.Enums; +using Core.Models.Utils; +using SptCommon.Extensions; namespace Core.Services; [Injectable(InjectionType.Singleton)] -public class RagfairLinkedItemService +public class RagfairLinkedItemService( + DatabaseService databaseService, + ItemHelper itemHelper, + ISptLogger logger +) { + protected Dictionary> linkedItemsCache = new(); + public HashSet GetLinkedItems(string linkedSearchId) { - throw new NotImplementedException(); + if (!linkedItemsCache.Keys.Any()) { + BuildLinkedItemTable(); + } + + return linkedItemsCache[linkedSearchId]; } - /// - /// Use ragfair linked item service to get an array of items that can fit on or in designated itemtpl - /// - /// Item to get sub-items for - /// TemplateItem array + /** + * Use ragfair linked item service to get an array of items that can fit on or in designated itemtpl + * @param itemTpl Item to get sub-items for + * @returns ITemplateItem array + */ public List GetLinkedDbItems(string itemTpl) { - throw new NotImplementedException(); + 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 - /// + /** + * Create Dictionary of every item and the items associated with it + */ protected void BuildLinkedItemTable() { - throw new NotImplementedException(); + var linkedItems = new Dictionary>(); + + foreach (var item in databaseService.GetItems().Values) { + var itemLinkedSet = GetLinkedItems(linkedItems, item.Id); + + ApplyLinkedItems(GetFilters(item, "Slots"), item, itemLinkedSet); + ApplyLinkedItems(GetFilters(item, "Chambers"), item, itemLinkedSet); + ApplyLinkedItems(GetFilters(item, "Cartridges"), item, itemLinkedSet); + + // Edge case, ensure ammo for revolves is included + if (item.Parent == BaseClasses.REVOLVER) { + // Find magazine for revolver + AddRevolverCylinderAmmoToLinkedItems(item, itemLinkedSet); + } + } + + linkedItemsCache = linkedItems; } - /// - /// Add ammo to revolvers linked item dictionary - /// - /// Revolvers cylinder - /// - protected void AddRevolverCylinderAmmoToLinkedItems( - TemplateItem cylinder, - Action> applyLinkedItems) + protected void ApplyLinkedItems(List items, TemplateItem item, HashSet itemLinkedSet) { - throw new NotImplementedException(); + foreach (var linkedItemId in items) { + itemLinkedSet.Add(linkedItemId); + GetLinkedItems(linkedItemId).Add(item.Id); + } } - /// - /// Scans a given slot type for filters and returns them as a List - /// - /// - /// - /// List of ids + protected HashSet GetLinkedItems(Dictionary> linkedItems, string id) + { + if (!linkedItems.ContainsKey(id)) { + linkedItems.Add(id, []); + } + return linkedItems[id]; + } + + /** + * Add ammo to revolvers linked item dictionary + * @param cylinder Revolvers cylinder + * @param applyLinkedItems + */ + protected void AddRevolverCylinderAmmoToLinkedItems(TemplateItem cylinder, HashSet itemLinkedSet) + { + var cylinderMod = cylinder.Properties.Slots?.FirstOrDefault((x) => x.Name == "mod_magazine"); + if (cylinderMod != null) { + // Get the first cylinder filter tpl + var cylinderTpl = cylinderMod.Props?.Filters?[0].Filter?[0]; + if (!string.IsNullOrEmpty(cylinderTpl)) { + // Get db data for cylinder tpl, add found slots info (camora_xxx) to linked items on revolver weapon + var cylinderItem = itemHelper.GetItem(cylinderTpl).Value; + ApplyLinkedItems(GetFilters(cylinderItem, "Slots"), cylinder, itemLinkedSet); + } + } + } + + /** + * Scans a given slot type for filters and returns them as a Set + * @param item + * @param slot + * @returns array of ids + */ protected List GetFilters(TemplateItem item, string slot) { - throw new NotImplementedException(); + var properties = item.Properties.GetAllPropsAsDict(); + if (!properties.TryGetValue(slot, out var value) || value == null) { + // item slot doesnt exist + return []; + } +/* + var filters = new List(); + // I have no fucking clue wtf is happening here... god help us all and anyone who has to read this code + foreach (var sub in properties[slot].GetAllPropsAsDict()) { + if (!("_props" in sub && "filters" in sub._props)) { + // not a filter + continue; + } + + for (var filter of sub._props.filters) { + for (var f of filter.Filter) { + filters.push(f); + } + } + } +*/ + return new List(); } } diff --git a/SptCommon/Extensions/ObjectExtensions.cs b/SptCommon/Extensions/ObjectExtensions.cs index a481be01..9d31f759 100644 --- a/SptCommon/Extensions/ObjectExtensions.cs +++ b/SptCommon/Extensions/ObjectExtensions.cs @@ -65,9 +65,9 @@ namespace SptCommon.Extensions return result; } - public static Dictionary GetAllPropsAsDict(this object? obj) + public static Dictionary GetAllPropsAsDict(this object? obj) { - var result = new Dictionary(); + var result = new Dictionary(); var props = obj.GetType().GetProperties(); foreach (var prop in props)