diff --git a/src/Sidekick.Apis.Poe/Clients/PoeTradeHandler.cs b/src/Sidekick.Apis.Poe/Clients/PoeTradeHandler.cs index 045dd95a0..7f81c15d9 100644 --- a/src/Sidekick.Apis.Poe/Clients/PoeTradeHandler.cs +++ b/src/Sidekick.Apis.Poe/Clients/PoeTradeHandler.cs @@ -34,17 +34,6 @@ protected override async Task SendAsync(HttpRequestMessage return response; } - if (response.StatusCode == HttpStatusCode.TooManyRequests) - { - var errorResponse = await ParseErrorResponse(response); - throw new SidekickException("Rate limit exceeded.", "The official trade website has a rate limit to avoid spam. Sidekick cannot change this.", errorResponse?.Error?.Message ?? string.Empty); - } - - if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb) - { - response = await HandleRedirect(request, response, cancellationToken); - } - if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb) { logger.LogWarning("[PoeTradeHandler] Received redirect response."); @@ -64,10 +53,9 @@ protected override async Task SendAsync(HttpRequestMessage } } - // Sidekick does not support authentication yet. - if (response.StatusCode == HttpStatusCode.Unauthorized) + if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb) { - throw new SidekickException("Sidekick failed to communicate with the trade API.", "The trade website requires authentication, which Sidekick does not support currently.", "Try using a different game language and/or force to search using English only in the settings."); + response = await HandleRedirect(request, response, cancellationToken); } // 403 probably means a cloudflare issue. @@ -108,6 +96,27 @@ protected override async Task SendAsync(HttpRequestMessage logger.LogWarning("[PoeTradeHandler] Query Failed: {responseCode} {responseMessage}", response.StatusCode, content); logger.LogWarning("[PoeTradeHandler] Uri: {uri}", request.RequestUri); logger.LogWarning("[PoeTradeHandler] Body: {uri}", body); + + var errorResponse = await ParseErrorResponse(response); + if (response.StatusCode == HttpStatusCode.BadRequest) + { + if (errorResponse?.Error?.Message?.StartsWith("Query is too complex.") ?? false) + { + throw new SidekickException("Query is too complex.", "The official trade website has limit on complex queries. Sidekick cannot change this.", "Use the official website to search for your current item."); + } + } + + if (response.StatusCode == HttpStatusCode.TooManyRequests) + { + throw new SidekickException("Rate limit exceeded.", "The official trade website has a rate limit to avoid spam. Sidekick cannot change this.", errorResponse?.Error?.Message ?? string.Empty); + } + + // Sidekick does not support authentication yet. + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new SidekickException("Sidekick failed to communicate with the trade API.", "The trade website requires authentication, which Sidekick does not support currently.", "Try using a different game language and/or force to search using English only in the settings."); + } + throw new ApiErrorException(); } diff --git a/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs b/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs index 429a2b335..6f1202dd6 100644 --- a/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs +++ b/src/Sidekick.Apis.Poe/Modifiers/InvariantModifierProvider.cs @@ -128,11 +128,21 @@ public async Task> GetList() var game = leagueId.GetGameFromLeagueId(); var cacheKey = $"{game.GetValueAttribute()}_InvariantModifiers"; - return await cacheProvider.GetOrSet(cacheKey, + var apiCategories = await cacheProvider.GetOrSet(cacheKey, async () => { var result = await poeTradeClient.Fetch(game, gameLanguageProvider.InvariantLanguage, "data/stats"); return result.Result; }, (cache) => cache.Any()); + + apiCategories.ForEach(category => + { + category.Entries.ForEach(entry => + { + entry.Text = ModifierProvider.RemoveSquareBrackets(entry.Text); + }); + }); + + return apiCategories; } } diff --git a/src/Sidekick.Apis.Poe/Parser/ItemParser.cs b/src/Sidekick.Apis.Poe/Parser/ItemParser.cs index 69c985569..7de6b29ff 100644 --- a/src/Sidekick.Apis.Poe/Parser/ItemParser.cs +++ b/src/Sidekick.Apis.Poe/Parser/ItemParser.cs @@ -7,10 +7,9 @@ using Sidekick.Apis.Poe.Parser.Modifiers; using Sidekick.Apis.Poe.Parser.Patterns; using Sidekick.Apis.Poe.Parser.Properties; +using Sidekick.Apis.Poe.Parser.Pseudo; using Sidekick.Apis.Poe.Parser.Sockets; -using Sidekick.Apis.Poe.Pseudo; using Sidekick.Common.Exceptions; -using Sidekick.Common.Game; using Sidekick.Common.Game.Items; namespace Sidekick.Apis.Poe.Parser @@ -20,7 +19,7 @@ public class ItemParser ILogger logger, IMetadataParser metadataProvider, IModifierParser modifierParser, - IPseudoModifierProvider pseudoModifierProvider, + IPseudoParser pseudoParser, IParserPatterns patterns, ClusterJewelParser clusterJewelParser, IInvariantMetadataProvider invariantMetadataProvider, @@ -72,7 +71,7 @@ public Item ParseItem(string itemText) var sockets = socketParser.Parse(parsingItem); var modifierLines = ParseModifiers(parsingItem); var properties = propertyParser.Parse(parsingItem, modifierLines); - var pseudoModifiers = parsingItem.Metadata.Game == GameType.PathOfExile ? ParsePseudoModifiers(modifierLines) : []; + var pseudoModifiers = pseudoParser.Parse(modifierLines); var item = new Item(metadata: metadata, invariant: invariant, header: header, @@ -137,16 +136,6 @@ private List ParseModifiers(ParsingItem parsingItem) }; } - private List ParsePseudoModifiers(List modifierLines) - { - if (modifierLines.Count == 0) - { - return new(); - } - - return pseudoModifierProvider.Parse(modifierLines); - } - #region Helpers private static bool GetBool(Regex pattern, ParsingItem parsingItem) diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ChaosResistancesDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ChaosResistancesDefinition.cs new file mode 100644 index 000000000..12988a55c --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ChaosResistancesDefinition.cs @@ -0,0 +1,19 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class ChaosResistancesDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_chaos_resistance" : null; + + protected override List Patterns => + [ + new(new Regex("to Chaos Resistance$")), + new(new Regex("(?=.*Chaos)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$")), + ]; + + protected override Regex Exception => new("Minions|Enemies|Totems"); +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/DexterityDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/DexterityDefinition.cs new file mode 100644 index 000000000..e7b9de213 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/DexterityDefinition.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class DexterityDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_dexterity" : null; + + protected override List Patterns => + [ + new(new Regex("to Dexterity$")), + new(new Regex("(?=.*Dexterity)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), + new(new Regex("to all Attributes$")), + ]; + + protected override Regex Exception => new("Passive"); +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ElementalResistancesDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ElementalResistancesDefinition.cs new file mode 100644 index 000000000..2f5dd3999 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ElementalResistancesDefinition.cs @@ -0,0 +1,21 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class ElementalResistancesDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_elemental_resistance" : null; + + protected override List Patterns => + [ + new(new Regex("to (?:Fire|Cold|Lightning) Resistance$")), + new(new Regex("to (?:Fire|Cold|Lightning) and (?:Fire|Cold|Lightning) Resistances$"), 2), + new(new Regex("(?=.*Chaos)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$")), + new(new Regex("to all Elemental Resistances$"), 3), + ]; + + protected override Regex Exception => new("Minions|Enemies|Totems"); +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/IntelligenceDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/IntelligenceDefinition.cs new file mode 100644 index 000000000..6a8dae4da --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/IntelligenceDefinition.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class IntelligenceDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_intelligence" : null; + + protected override List Patterns => + [ + new(new Regex("to Intelligence$")), + new(new Regex("(?=.*Intelligence)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), + new(new Regex("to all Attributes$")), + ]; + + protected override Regex Exception => new("Passive"); +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/LifeDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/LifeDefinition.cs new file mode 100644 index 000000000..ed0c80adb --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/LifeDefinition.cs @@ -0,0 +1,23 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class LifeDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_life" : null; + + protected override List Patterns => + [ + new(new Regex("to maximum Life$")), + new(new Regex("to Strength$"), AttributeMultiplier), + new(new Regex("(?=.*Strength)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$"), AttributeMultiplier), + new(new Regex("to all Attributes$"), AttributeMultiplier), + ]; + + protected override Regex Exception => new("Zombies|Transformed"); + + private double AttributeMultiplier => game == GameType.PathOfExile ? 0.5 : 2; +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ManaDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ManaDefinition.cs new file mode 100644 index 000000000..e3d65a9b8 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/ManaDefinition.cs @@ -0,0 +1,23 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class ManaDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_mana" : null; + + protected override List Patterns => + [ + new PseudoPattern(new Regex("to maximum Mana$")), + new PseudoPattern(new Regex("to Intelligence$"), AttributeMultiplier), + new PseudoPattern(new Regex("(?=.*Intelligence)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$"), AttributeMultiplier), + new PseudoPattern(new Regex("to all Attributes$"), AttributeMultiplier), + ]; + + protected override Regex Exception => new("Zombies|Transformed"); + + private double AttributeMultiplier => game == GameType.PathOfExile ? 0.5 : 2; +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/StrengthDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/StrengthDefinition.cs new file mode 100644 index 000000000..faa9048f7 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/Definitions/StrengthDefinition.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; +using Sidekick.Common.Game; + +namespace Sidekick.Apis.Poe.Parser.Pseudo.Definitions; + +public class StrengthDefinition(GameType game) : PseudoDefinition +{ + protected override bool Enabled => game == GameType.PathOfExile; + + protected override string? ModifierId => game == GameType.PathOfExile ? "pseudo.pseudo_total_strength" : null; + + protected override List Patterns => + [ + new(new Regex("to Strength$")), + new(new Regex("(?=.*Strength)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), + new(new Regex("to all Attributes$")), + ]; + + protected override Regex Exception => new("Passive"); +} diff --git a/src/Sidekick.Apis.Poe/Pseudo/IPseudoModifierProvider.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/IPseudoParser.cs similarity index 59% rename from src/Sidekick.Apis.Poe/Pseudo/IPseudoModifierProvider.cs rename to src/Sidekick.Apis.Poe/Parser/Pseudo/IPseudoParser.cs index 6f7bfff1c..ba64dcc71 100644 --- a/src/Sidekick.Apis.Poe/Pseudo/IPseudoModifierProvider.cs +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/IPseudoParser.cs @@ -1,9 +1,9 @@ using Sidekick.Common.Game.Items; using Sidekick.Common.Initialization; -namespace Sidekick.Apis.Poe.Pseudo +namespace Sidekick.Apis.Poe.Parser.Pseudo { - public interface IPseudoModifierProvider : IInitializableService + public interface IPseudoParser : IInitializableService { List Parse(List modifiers); } diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoDefinition.cs new file mode 100644 index 000000000..b14fd6959 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoDefinition.cs @@ -0,0 +1,144 @@ +using System.Text.RegularExpressions; +using Sidekick.Apis.Poe.Modifiers.Models; +using Sidekick.Common.Game.Items; + +namespace Sidekick.Apis.Poe.Parser.Pseudo; + +public abstract class PseudoDefinition +{ + private static readonly Regex parseHashPattern = new("\\#"); + + protected abstract bool Enabled { get; } + + protected abstract string? ModifierId { get; } + + protected abstract List Patterns { get; } + + /// + /// Represents a regular expression pattern used to exclude certain modifier texts + /// during the processing of pseudo-modifier definitions in the Path of Exile API. + /// + /// + /// This property defines a regular expression that matches modifier texts + /// which should be excluded from further processing. Each derived class provides + /// a specific implementation of this property. It is utilized within the + /// initialization and parsing processes to filter out unwanted modifier entries. + /// + protected abstract Regex? Exception { get; } + + private string? Text { get; set; } + + public List Modifiers { get; private set; } = new(); + + internal void InitializeDefinition(List apiCategories, List? localizedPseudoModifiers) + { + foreach (var apiCategory in apiCategories) + { + foreach (var apiModifier in apiCategory.Entries) + { + if (!Enabled) + { + return; + } + + if (Exception != null && Exception.IsMatch(apiModifier.Text)) + { + continue; + } + + foreach (var pattern in Patterns) + { + if (apiModifier.Id == null || apiModifier.Type == null || apiModifier.Text == null) + { + continue; + } + + if (pattern.Pattern.IsMatch(apiModifier.Text)) + { + Modifiers.Add(new PseudoModifierDefinition(apiModifier.Id, apiModifier.Type, apiModifier.Text, pattern.Multiplier)); + } + } + } + } + + Modifiers = Modifiers.OrderBy(x => x.Type switch + { + "pseudo" => 0, + "explicit" => 1, + "implicit" => 2, + "crafted" => 3, + "enchant" => 4, + "fractured" => 5, + "veiled" => 6, + _ => 7, + }) + .ThenBy(x => x.Text) + .ToList(); + + if (localizedPseudoModifiers != null && !string.IsNullOrEmpty(ModifierId)) + { + Text = localizedPseudoModifiers.FirstOrDefault(x => x.Id == ModifierId)?.Text ?? ""; + } + + if (string.IsNullOrEmpty(Text)) + { + Text = string.Join(", ", Modifiers.Select(x => x.Text).Distinct().ToList()); + } + } + + internal PseudoModifier? Parse(List itemModifierLines) + { + if (!Enabled || !HasPseudoMods(itemModifierLines)) + { + return null; + } + + var result = new PseudoModifier() + { + ModifierId = ModifierId, + Text = Text ?? string.Empty, + }; + + if (string.IsNullOrEmpty(ModifierId)) + { + foreach (var definitionModifier in Modifiers) + { + result.WeightedSumModifiers.Add(definitionModifier.Id, definitionModifier.Multiplier); + } + } + + foreach (var itemModifierLine in itemModifierLines) + { + foreach (var definitionModifier in Modifiers) + { + if (itemModifierLine.Modifiers.All(itemModifier => definitionModifier.Id != itemModifier.Id) || itemModifierLine.Values.Count == 0) + { + continue; + } + + result.Value += itemModifierLine.Values.Average() * definitionModifier.Multiplier; + break; + } + } + + result.Value = (int)result.Value; + result.Text = parseHashPattern.Replace(result.Text, ((int)result.Value).ToString(), 1); + return result; + } + + private bool HasPseudoMods(List itemModifierLines) + { + foreach (var definitionModifier in Modifiers) + { + foreach (var itemModifierLine in itemModifierLines) + { + foreach (var modifier in itemModifierLine.Modifiers) + { + if (modifier.Id == definitionModifier.Id) return true; + } + } + } + + return false; + } +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoModifierDefinition.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoModifierDefinition.cs new file mode 100644 index 000000000..3ff398e53 --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoModifierDefinition.cs @@ -0,0 +1,24 @@ +namespace Sidekick.Apis.Poe.Parser.Pseudo +{ + public class PseudoModifierDefinition + ( + string id, + string type, + string text, + double multiplier + ) + { + public string Id { get; } = id; + + public string Type { get; } = type; + + public string Text { get; } = text; + + public double Multiplier { get; } = multiplier; + + public override string ToString() + { + return $"{Text} - {Multiplier}x ({Type})"; + } + } +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoParser.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoParser.cs new file mode 100644 index 000000000..a21acd22d --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoParser.cs @@ -0,0 +1,61 @@ +using Sidekick.Apis.Poe.Modifiers; +using Sidekick.Apis.Poe.Parser.Pseudo.Definitions; +using Sidekick.Common.Extensions; +using Sidekick.Common.Game.Items; +using Sidekick.Common.Settings; + +namespace Sidekick.Apis.Poe.Parser.Pseudo; + +public class PseudoParser +( + IInvariantModifierProvider invariantModifierProvider, + IModifierProvider modifierProvider, + ISettingsService settingsService +) : IPseudoParser +{ + private List Definitions { get; } = new(); + + /// + public int Priority => 200; + + /// + public async Task Initialize() + { + var leagueId = await settingsService.GetString(SettingKeys.LeagueId); + var game = leagueId.GetGameFromLeagueId(); + + Definitions.Clear(); + Definitions.AddRange([ + new ElementalResistancesDefinition(game), + new ChaosResistancesDefinition(game), + new StrengthDefinition(game), + new IntelligenceDefinition(game), + new DexterityDefinition(game), + new LifeDefinition(game), + new ManaDefinition(game), + ]); + + var categories = await invariantModifierProvider.GetList(); + categories.RemoveAll(x => x.Entries.FirstOrDefault()?.Id.StartsWith("pseudo") == true); + + var localizedPseudoModifiers = modifierProvider.Patterns.GetValueOrDefault(ModifierCategory.Pseudo); + + foreach (var definition in Definitions) + { + definition.InitializeDefinition(categories, localizedPseudoModifiers); + } + } + + public List Parse(List lines) + { + var results = new List(); + + foreach (var definition in Definitions) + { + var result = definition.Parse(lines); + if (result != null) results.Add(result); + } + + return results; + } +} diff --git a/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoPattern.cs b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoPattern.cs new file mode 100644 index 000000000..2beb360ac --- /dev/null +++ b/src/Sidekick.Apis.Poe/Parser/Pseudo/PseudoPattern.cs @@ -0,0 +1,14 @@ +using System.Text.RegularExpressions; + +namespace Sidekick.Apis.Poe.Parser.Pseudo; + +public class PseudoPattern +( + Regex regex, + double multiplier = 1 +) +{ + public Regex Pattern { get; set; } = regex; + + public double Multiplier { get; set; } = multiplier; +} diff --git a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinition.cs b/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinition.cs deleted file mode 100644 index c6888ea94..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinition.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Sidekick.Apis.Poe.Pseudo.Models -{ - public class PseudoDefinition - { - public PseudoDefinition(string id, string text) - { - Id = id; - Text = text; - } - - public string Id { get; } - - public string Text { get; } - - public List Modifiers { get; set; } = new(); - - public override string ToString() - { - if (Text == null) - { - return Id; - } - - return $"{Text} - {Id}"; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinitionModifier.cs b/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinitionModifier.cs deleted file mode 100644 index d74ce6cba..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoDefinitionModifier.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Sidekick.Apis.Poe.Pseudo.Models -{ - public class PseudoDefinitionModifier - { - public PseudoDefinitionModifier(string type, string text, double multiplier) - { - Type = type; - Text = text; - Multiplier = multiplier; - } - - public List Ids { get; set; } = new List(); - - public string Type { get; set; } - - public string Text { get; set; } - - public double Multiplier { get; set; } - - public override string ToString() - { - return $"{Text} - {Multiplier}x - {string.Join(", ", Ids)} ({Ids.Count})"; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPattern.cs b/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPattern.cs deleted file mode 100644 index 550a1bbdd..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPattern.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Sidekick.Apis.Poe.Pseudo.Models -{ - public class PseudoPattern - { - public PseudoPattern(Regex regex, double multiplier = 1) - { - Pattern = regex; - Multiplier = multiplier; - } - - public Regex Pattern { get; set; } - - public double Multiplier { get; set; } - - public List Matches { get; set; } = new List(); - } -} diff --git a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternGroup.cs b/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternGroup.cs deleted file mode 100644 index 5b0baca2a..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternGroup.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Text.RegularExpressions; - -namespace Sidekick.Apis.Poe.Pseudo.Models -{ - public class PseudoPatternGroup - { - public PseudoPatternGroup( - Modifiers.Models.ApiCategory apiCategory, - string id, - params PseudoPattern[] patterns) - : this(apiCategory, id, null, patterns) - { - } - - public PseudoPatternGroup( - Modifiers.Models.ApiCategory apiCategory, - string id, - Regex? exception, - params PseudoPattern[] patterns) - { - Id = id; - Exception = exception; - Patterns = patterns.ToList(); - - Text = apiCategory.Entries.First(x => x.Id == id).Text; - } - - public string Id { get; } - public Regex? Exception { get; } - public List Patterns { get; } - public string? Text { get; } - - public override string ToString() - { - if (Text == null) - { - return Id; - } - - return $"{Text} - {Id}"; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternMatch.cs b/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternMatch.cs deleted file mode 100644 index e4c1d5b2e..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/Models/PseudoPatternMatch.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Sidekick.Apis.Poe.Pseudo.Models -{ - public class PseudoPatternMatch - { - public PseudoPatternMatch(string id, string type, string text) - { - Id = id; - Type = type; - Text = text; - } - - public string Id { get; set; } - public string Type { get; set; } - public string Text { get; set; } - - public override string ToString() - { - return $"{Text} - {Id} ({Type})"; - } - } -} diff --git a/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs b/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs deleted file mode 100644 index 79bd7bc5e..000000000 --- a/src/Sidekick.Apis.Poe/Pseudo/PseudoModifierProvider.cs +++ /dev/null @@ -1,659 +0,0 @@ -using System.Text.RegularExpressions; -using Sidekick.Apis.Poe.Modifiers; -using Sidekick.Apis.Poe.Modifiers.Models; -using Sidekick.Apis.Poe.Pseudo.Models; -using Sidekick.Common.Game.Items; - -namespace Sidekick.Apis.Poe.Pseudo -{ - public class PseudoModifierProvider(IInvariantModifierProvider invariantModifierProvider) : IPseudoModifierProvider - { - private readonly Regex ParseHashPattern = new("\\#"); - - private List Definitions { get; } = new(); - - /// - public int Priority => 200; - - /// - public async Task Initialize() - { - if (Definitions.Count > 0) - { - return; - } - - var result = await invariantModifierProvider.GetList(); - var groups = InitializeGroups(result); - - foreach (var category in result) - { - var first = category.Entries.FirstOrDefault(); - if (first == null || first.Id?.Split('.').First() == "pseudo") - { - continue; - } - - foreach (var entry in category.Entries) - { - foreach (var group in groups) - { - if (group.Exception != null && group.Exception.IsMatch(entry.Text ?? string.Empty)) - { - continue; - } - - foreach (var pattern in group.Patterns) - { - if (entry.Id == null || entry.Type == null || entry.Text == null) - { - continue; - } - - if (pattern.Pattern.IsMatch(entry.Text)) - { - pattern.Matches.Add(new PseudoPatternMatch(entry.Id, entry.Type, entry.Text)); - } - } - } - } - } - - foreach (var group in groups) - { - if (group.Text == null) - { - continue; - } - - var definition = new PseudoDefinition(group.Id, group.Text); - - foreach (var pattern in group.Patterns) - { - PseudoDefinitionModifier? modifier = null; - - foreach (var match in pattern.Matches.OrderBy(x => x.Type).ThenBy(x => x.Text.Length)) - { - if (modifier != null) - { - if (modifier.Type != match.Type) - { - modifier = null; - } - else if (!match.Text.StartsWith(modifier.Text)) - { - modifier = null; - } - } - - if (modifier == null) - { - modifier = new PseudoDefinitionModifier(match.Type, match.Text, pattern.Multiplier); - } - - modifier.Ids.Add(match.Id); - - if (!definition.Modifiers.Contains(modifier)) - { - definition.Modifiers.Add(modifier); - } - } - } - - Definitions.Add(definition); - } - } - - private static List InitializeGroups(List categories) - { - var pseudoCategory = categories - .FirstOrDefault(x => x.Entries.FirstOrDefault()?.Id?.Split('.').FirstOrDefault() == "pseudo"); - if (pseudoCategory == null) - { - return new(); - } - - var groups = new List() { - // +#% total to Cold Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_cold_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to Cold Resistance$")), - new PseudoPattern(new Regex("(?=.*Cold)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$"))), - - // +#% total to Fire Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_fire_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to Fire Resistance$")), - new PseudoPattern(new Regex("(?=.*Fire)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$"))), - - // +#% total to Lightning Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_lightning_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to Lightning Resistance$")), - new PseudoPattern(new Regex("(?=.*Lightning)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$"))), - - // +#% total to Chaos Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_chaos_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to Chaos Resistance$")), - new PseudoPattern(new Regex("(?=.*Chaos)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$"))), - - // +#% total to all Elemental Resistances - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_all_elemental_resistances", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to all Elemental Resistances$"))), - - // +#% total Elemental Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_elemental_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to (?:Fire|Cold|Lightning) Resistance$")), - new PseudoPattern(new Regex("to (?:Fire|Cold|Lightning) and (?:Fire|Cold|Lightning) Resistances$"), 2), - new PseudoPattern(new Regex("(?=.*Chaos)to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$")), - new PseudoPattern(new Regex("to all Elemental Resistances$"), 3)), - - // +#% total Resistance - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_resistance", - exception: new Regex("Minions|Enemies|Totems"), - new PseudoPattern(new Regex("to (Fire|Cold|Lightning|Chaos) Resistance$")), - new PseudoPattern(new Regex("to (?:Fire|Cold|Lightning|Chaos) and (?:Fire|Cold|Lightning|Chaos) Resistances$"), 2), - new PseudoPattern(new Regex("to all Elemental Resistances$"), 3)), - - // # total Resistances - // pseudo.pseudo_count_resistances - - // +# total to Strength - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_strength", - exception: new Regex("Passive"), - new PseudoPattern(new Regex("to Strength$")), - new PseudoPattern(new Regex("(?=.*Strength)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), - new PseudoPattern(new Regex("to all Attributes$"))), - - // +# total to Dexterity - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_dexterity", - exception: new Regex("Passive"), - new PseudoPattern(new Regex("to Dexterity$")), - new PseudoPattern(new Regex("(?=.*Dexterity)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), - new PseudoPattern(new Regex("to all Attributes$"))), - - // +# total to Intelligence - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_intelligence", - exception: new Regex("Passive"), - new PseudoPattern(new Regex("to Intelligence$")), - new PseudoPattern(new Regex("(?=.*Intelligence)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$")), - new PseudoPattern(new Regex("to all Attributes$"))), - - // +# total to all Attributes - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_all_attributes", - exception: new Regex("Passive"), - new PseudoPattern(new Regex("to all Attributes$"))), - - // +# total maximum Life - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_life", - exception: new Regex("Zombies|Transformed"), - new PseudoPattern(new Regex("to maximum Life$")), - new PseudoPattern(new Regex("to Strength$"), 0.5), - new PseudoPattern(new Regex("(?=.*Strength)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$"), 0.5), - new PseudoPattern(new Regex("to all Attributes$"), 0.5)), - - // +# total maximum Mana - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_mana", - exception: new Regex("Transformed"), - new PseudoPattern(new Regex("to maximum Mana$")), - new PseudoPattern(new Regex("to Intelligence$"), 0.5), - new PseudoPattern(new Regex("(?=.*Intelligence)to (?:Strength|Dexterity|Intelligence) and (?:Strength|Dexterity|Intelligence)$"), 0.5), - new PseudoPattern(new Regex("to all Attributes$"), 0.5)), - - // +# total maximum Energy Shield - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_energy_shield", - new PseudoPattern(new Regex("to maximum Energy Shield$"))), - - // #% total increased maximum Energy Shield - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_increased_energy_shield", - new PseudoPattern(new Regex("% increased maximum Energy Shield$"))), - - // +#% total Attack Speed - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_attack_speed", - new PseudoPattern(new Regex("^\\#% increased Attack Speed$"))), - - // +#% total Cast Speed - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_total_cast_speed", - new PseudoPattern(new Regex("^\\#% increased Cast Speed$"))), - - // #% increased Movement Speed - new PseudoPatternGroup( - apiCategory: pseudoCategory, - id: "pseudo.pseudo_increased_movement_speed", - new PseudoPattern(new Regex("^\\#% increased Movement Speed$"))), - - // #% total increased Physical Damage - // pseudo.pseudo_increased_physical_damage - - // +#% Global Critical Strike Chance - // pseudo.pseudo_global_critical_strike_chance - - // +#% total Critical Strike Chance for Spells - // pseudo.pseudo_critical_strike_chance_for_spells - - // +#% Global Critical Strike Multiplier - // pseudo.pseudo_global_critical_strike_multiplier - - // Adds # to # Physical Damage - // pseudo.pseudo_adds_physical_damage - - // Adds # to # Lightning Damage - // pseudo.pseudo_adds_lightning_damage - - // Adds # to # Cold Damage - // pseudo.pseudo_adds_cold_damage - - // Adds # to # Fire Damage - // pseudo.pseudo_adds_fire_damage - - // Adds # to # Elemental Damage - // pseudo.pseudo_adds_elemental_damage - - // Adds # to # Chaos Damage - // pseudo.pseudo_adds_chaos_damage - - // Adds # to # Damage - // pseudo.pseudo_adds_damage - - // Adds # to # Physical Damage to Attacks - // pseudo.pseudo_adds_physical_damage_to_attacks - - // Adds # to # Lightning Damage to Attacks - // pseudo.pseudo_adds_lightning_damage_to_attacks - - // Adds # to # Cold Damage to Attacks - // pseudo.pseudo_adds_cold_damage_to_attacks - - // Adds # to # Fire Damage to Attacks - // pseudo.pseudo_adds_fire_damage_to_attacks - - // Adds # to # Elemental Damage to Attacks - // pseudo.pseudo_adds_elemental_damage_to_attacks - - // Adds # to # Chaos Damage to Attacks - // pseudo.pseudo_adds_chaos_damage_to_attacks - - // Adds # to # Damage to Attacks - // pseudo.pseudo_adds_damage_to_attacks - - // Adds # to # Physical Damage to Spells - // pseudo.pseudo_adds_physical_damage_to_spells - - // Adds # to # Lightning Damage to Spells - // pseudo.pseudo_adds_lightning_damage_to_spells - - // Adds # to # Cold Damage to Spells - // pseudo.pseudo_adds_cold_damage_to_spells - - // Adds # to # Fire Damage to Spells - // pseudo.pseudo_adds_fire_damage_to_spells - - // Adds # to # Elemental Damage to Spells - // pseudo.pseudo_adds_elemental_damage_to_spells - - // Adds # to # Chaos Damage to Spells - // pseudo.pseudo_adds_chaos_damage_to_spells - - // Adds # to # Damage to Spells - // pseudo.pseudo_adds_damage_to_spells - - // #% increased Elemental Damage - // pseudo.pseudo_increased_elemental_damage - - // #% increased Lightning Damage - // pseudo.pseudo_increased_lightning_damage - - // #% increased Cold Damage - // pseudo.pseudo_increased_cold_damage - - // #% increased Fire Damage - // pseudo.pseudo_increased_fire_damage - - // #% increased Spell Damage - // pseudo.pseudo_increased_spell_damage - - // #% increased Lightning Spell Damage - // pseudo.pseudo_increased_lightning_spell_damage - - // #% increased Cold Spell Damage - // pseudo.pseudo_increased_cold_spell_damage - - // #% increased Fire Spell Damage - // pseudo.pseudo_increased_fire_spell_damage - - // #% increased Lightning Damage with Attack Skills - // pseudo.pseudo_increased_lightning_damage_with_attack_skills - - // #% increased Cold Damage with Attack Skills - // pseudo.pseudo_increased_cold_damage_with_attack_skills - - // #% increased Fire Damage with Attack Skills - // pseudo.pseudo_increased_fire_damage_with_attack_skills - - // #% increased Elemental Damage with Attack Skills - // pseudo.pseudo_increased_elemental_damage_with_attack_skills - - // #% increased Rarity of Items found - // pseudo.pseudo_increased_rarity - - // #% increased Burning Damage - // pseudo.pseudo_increased_burning_damage - - // # Life Regenerated per Second - // pseudo.pseudo_total_life_regen - - // #% of Life Regenerated per Second - // pseudo.pseudo_percent_life_regen - - // #% of Physical Attack Damage Leeched as Life - // pseudo.pseudo_physical_attack_damage_leeched_as_life - - // #% of Physical Attack Damage Leeched as Mana - // pseudo.pseudo_physical_attack_damage_leeched_as_mana - - // #% increased Mana Regeneration Rate - // pseudo.pseudo_increased_mana_regen - - // +# total to Level of Socketed Gems - // pseudo.pseudo_total_additional_gem_levels - - // +# total to Level of Socketed Elemental Gems - // pseudo.pseudo_total_additional_elemental_gem_levels - - // +# total to Level of Socketed Fire Gems - // pseudo.pseudo_total_additional_fire_gem_levels - - // +# total to Level of Socketed Cold Gems - // pseudo.pseudo_total_additional_cold_gem_levels - - // +# total to Level of Socketed Lightning Gems - // pseudo.pseudo_total_additional_lightning_gem_levels - - // +# total to Level of Socketed Chaos Gems - // pseudo.pseudo_total_additional_chaos_gem_levels - - // +# total to Level of Socketed Spell Gems - // pseudo.pseudo_total_additional_spell_gem_levels - - // +# total to Level of Socketed Projectile Gems - // pseudo.pseudo_total_additional_projectile_gem_levels - - // +# total to Level of Socketed Bow Gems - // pseudo.pseudo_total_additional_bow_gem_levels - - // +# total to Level of Socketed Melee Gems - // pseudo.pseudo_total_additional_melee_gem_levels - - // +# total to Level of Socketed Minion Gems - // pseudo.pseudo_total_additional_minion_gem_levels - - // +# total to Level of Socketed Strength Gems - // pseudo.pseudo_total_additional_strength_gem_levels - - // +# total to Level of Socketed Dexterity Gems - // pseudo.pseudo_total_additional_dexterity_gem_levels - - // +# total to Level of Socketed Intelligence Gems - // pseudo.pseudo_total_additional_intelligence_gem_levels - - // +# total to Level of Socketed Aura Gems - // pseudo.pseudo_total_additional_aura_gem_levels - - // +# total to Level of Socketed Movement Gems - // pseudo.pseudo_total_additional_movement_gem_levels - - // +# total to Level of Socketed Curse Gems - // pseudo.pseudo_total_additional_curse_gem_levels - - // +# total to Level of Socketed Vaal Gems - // pseudo.pseudo_total_additional_vaal_gem_levels - - // +# total to Level of Socketed Support Gems - // pseudo.pseudo_total_additional_support_gem_levels - - // +# total to Level of Socketed Skill Gems - // pseudo.pseudo_total_additional_skill_gem_levels - - // +# total to Level of Socketed Warcry Gems - // pseudo.pseudo_total_additional_warcry_gem_levels - - // +# total to Level of Socketed Golem Gems - // pseudo.pseudo_total_additional_golem_gem_levels - - // # Implicit Modifiers - // pseudo.pseudo_number_of_implicit_mods - - // # Prefix Modifiers - // pseudo.pseudo_number_of_prefix_mods - - // # Suffix Modifiers - // pseudo.pseudo_number_of_suffix_mods - - // # Modifiers - // pseudo.pseudo_number_of_affix_mods - - // # Crafted Prefix Modifiers - // pseudo.pseudo_number_of_crafted_prefix_mods - - // # Crafted Suffix Modifiers - // pseudo.pseudo_number_of_crafted_suffix_mods - - // # Crafted Modifiers - // pseudo.pseudo_number_of_crafted_mods - - // # Empty Prefix Modifiers - // pseudo.pseudo_number_of_empty_prefix_mods - - // # Empty Suffix Modifiers - // pseudo.pseudo_number_of_empty_suffix_mods - - // # Empty Modifiers - // pseudo.pseudo_number_of_empty_affix_mods - - // # Incubator Kills (Whispering) - // pseudo.pseudo_whispering_incubator_kills - - // # Incubator Kills (Fine) - // pseudo.pseudo_fine_incubator_kills - - // # Incubator Kills (Singular) - // pseudo.pseudo_singular_incubator_kills - - // # Incubator Kills (Cartographer's) - // pseudo.pseudo_cartographers_incubator_kills - - // # Incubator Kills (Otherwordly) - // pseudo.pseudo_otherworldly_incubator_kills - - // # Incubator Kills (Abyssal) - // pseudo.pseudo_abyssal_incubator_kills - - // # Incubator Kills (Fragmented) - // pseudo.pseudo_fragmented_incubator_kills - - // # Incubator Kills (Skittering) - // pseudo.pseudo_skittering_incubator_kills - - // # Incubator Kills (Infused) - // pseudo.pseudo_infused_incubator_kills - - // # Incubator Kills (Fossilised) - // pseudo.pseudo_fossilised_incubator_kills - - // # Incubator Kills (Decadent) - // pseudo.pseudo_decadent_incubator_kills - - // # Incubator Kills (Diviner's) - // pseudo.pseudo_diviners_incubator_kills - - // # Incubator Kills (Primal) - // pseudo.pseudo_primal_incubator_kills - - // # Incubator Kills (Enchanted) - // pseudo.pseudo_enchanted_incubator_kills - - // # Incubator Kills (Geomancer's) - // pseudo.pseudo_geomancers_incubator_kills - - // # Incubator Kills (Ornate) - // pseudo.pseudo_ornate_incubator_kills - - // # Incubator Kills (Time-Lost) - // pseudo.pseudo_timelost_incubator_kills - - // # Incubator Kills (Celestial Armoursmith's) - // pseudo.pseudo_celestial_armoursmiths_incubator_kills - - // # Incubator Kills (Celestial Blacksmith's) - // pseudo.pseudo_celestial_blacksmiths_incubator_kills - - // # Incubator Kills (Celestial Jeweller's) - // pseudo.pseudo_celestial_jewellers_incubator_kills - - // # Incubator Kills (Eldritch) - // pseudo.pseudo_eldritch_incubator_kills - - // # Incubator Kills (Obscured) - // pseudo.pseudo_obscured_incubator_kills - - // # Incubator Kills (Foreboding) - // pseudo.pseudo_foreboding_incubator_kills - - // # Incubator Kills (Thaumaturge's) - // pseudo.pseudo_thaumaturges_incubator_kills - - // # Incubator Kills (Mysterious) - // pseudo.pseudo_mysterious_incubator_kills - - // # Incubator Kills (Gemcutter's) - // pseudo.pseudo_gemcutters_incubator_kills - - // # Incubator Kills (Feral) - // pseudo.pseudo_feral_incubator_kills - - // # Fractured Modifiers - // pseudo.pseudo_number_of_fractured_mods - - // +#% Quality to Elemental Damage Modifiers - // pseudo.pseudo_jewellery_elemental_quality - - // +#% Quality to Caster Modifiers - // pseudo.pseudo_jewellery_caster_quality - - // +#% Quality to Attack Modifiers - // pseudo.pseudo_jewellery_attack_quality - - // +#% Quality to Defence Modifiers - // pseudo.pseudo_jewellery_defense_quality - - // +#% Quality to Life and Mana Modifiers - // pseudo.pseudo_jewellery_resource_quality - - // +#% Quality to Resistance Modifiers - // pseudo.pseudo_jewellery_resistance_quality - - // +#% Quality to Attribute Modifiers - // pseudo.pseudo_jewellery_attribute_quality - }; - - return groups; - } - - public List Parse(List lines) - { - var modifiers = new List(); - - foreach (var line in lines) - { - var modifier = line.Modifiers.FirstOrDefault(); - if (modifier == null) - { - continue; - } - - FillPseudo(modifiers, line); - } - - modifiers.ForEach(x => - { - if (x.Text == null) - { - return; - } - - x.Text = ParseHashPattern.Replace(x.Text, ((int)x.Value).ToString(), 1); - }); - - return modifiers; - } - - private void FillPseudo(List modifiers, ModifierLine line) - { - foreach (var pseudoDefinition in Definitions) - { - foreach (var pseudoModifier in pseudoDefinition.Modifiers) - { - var lineModifier = line.Modifiers.FirstOrDefault(); - if (lineModifier == null || !pseudoModifier.Ids.Any(id => id == lineModifier.Id)) - { - continue; - } - - var modifier = modifiers.FirstOrDefault(x => x.Id == pseudoDefinition.Id); - if (modifier == null) - { - modifiers.Add(new PseudoModifier( - text: pseudoDefinition.Text) - { - Id = pseudoDefinition.Id, - Value = (int)(line.Values.FirstOrDefault() * pseudoModifier.Multiplier), - }); - } - else - { - modifier.Value += (int)(line.Values.FirstOrDefault() * pseudoModifier.Multiplier); - } - - break; - } - } - } - } -} diff --git a/src/Sidekick.Apis.Poe/StartupExtensions.cs b/src/Sidekick.Apis.Poe/StartupExtensions.cs index 1d17f0003..e6e6c66ca 100644 --- a/src/Sidekick.Apis.Poe/StartupExtensions.cs +++ b/src/Sidekick.Apis.Poe/StartupExtensions.cs @@ -18,8 +18,8 @@ using Sidekick.Apis.Poe.Parser.Modifiers; using Sidekick.Apis.Poe.Parser.Patterns; using Sidekick.Apis.Poe.Parser.Properties; +using Sidekick.Apis.Poe.Parser.Pseudo; using Sidekick.Apis.Poe.Parser.Sockets; -using Sidekick.Apis.Poe.Pseudo; using Sidekick.Apis.Poe.Stash; using Sidekick.Apis.Poe.Static; using Sidekick.Apis.Poe.Trade; @@ -73,7 +73,7 @@ public static IServiceCollection AddSidekickPoeApi(this IServiceCollection servi services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); - services.AddSidekickInitializableService(); + services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); services.AddSidekickInitializableService(); diff --git a/src/Sidekick.Apis.Poe/Trade/Models/PseudoModifierFilter.cs b/src/Sidekick.Apis.Poe/Trade/Models/PseudoModifierFilter.cs index 918653eda..df384a4e2 100644 --- a/src/Sidekick.Apis.Poe/Trade/Models/PseudoModifierFilter.cs +++ b/src/Sidekick.Apis.Poe/Trade/Models/PseudoModifierFilter.cs @@ -4,15 +4,9 @@ namespace Sidekick.Apis.Poe.Trade.Models { public class PseudoModifierFilter : ITradeFilter { - public PseudoModifierFilter(PseudoModifier modifier) - { - Modifier = modifier; - Checked = false; - } - - public PseudoModifier Modifier { get; } + public required PseudoModifier PseudoModifier { get; set; } - public bool? @Checked { get; set; } + public bool? @Checked { get; set; } = false; public decimal? Min { get; set; } @@ -25,13 +19,13 @@ public PseudoModifierFilter(PseudoModifier modifier) /// public void NormalizeMinValue() { - if (Modifier.Value > 0) + if (PseudoModifier.Value > 0) { - Min = (int)Math.Max((1 - NormalizeValue) * Modifier.Value, 0); + Min = (int)Math.Max((1 - NormalizeValue) * PseudoModifier.Value, 0); } else { - Min = (int)Math.Min((1 + NormalizeValue) * Modifier.Value, 0); + Min = (int)Math.Min((1 + NormalizeValue) * PseudoModifier.Value, 0); } } @@ -40,13 +34,13 @@ public void NormalizeMinValue() /// public void NormalizeMaxValue() { - if (Modifier.Value > 0) + if (PseudoModifier.Value > 0) { - Max = (int)Math.Max(Math.Max(Modifier.Value + 1, (1 + NormalizeValue) * Modifier.Value), 0); + Max = (int)Math.Max(Math.Max(PseudoModifier.Value + 1, (1 + NormalizeValue) * PseudoModifier.Value), 0); } else { - Max = (int)Math.Min(Math.Max(Modifier.Value + 1, (1 - NormalizeValue) * Modifier.Value), 0); + Max = (int)Math.Min(Math.Max(PseudoModifier.Value + 1, (1 - NormalizeValue) * PseudoModifier.Value), 0); } } @@ -55,8 +49,8 @@ public void NormalizeMaxValue() /// public void SetExactValue() { - Min = (int)Modifier.Value; - Max = (int)Modifier.Value; + Min = (int)PseudoModifier.Value; + Max = (int)PseudoModifier.Value; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/ArmourFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/ArmourFilters.cs index cf01063ad..cb18a8dea 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/ArmourFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/ArmourFilters.cs @@ -5,15 +5,15 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class ArmourFilters { [JsonPropertyName("ar")] - public SearchFilterValue? Armor { get; set; } + public StatFilterValue? Armor { get; set; } [JsonPropertyName("es")] - public SearchFilterValue? EnergyShield { get; set; } + public StatFilterValue? EnergyShield { get; set; } [JsonPropertyName("ev")] - public SearchFilterValue? Evasion { get; set; } + public StatFilterValue? Evasion { get; set; } [JsonPropertyName("block")] - public SearchFilterValue? Block { get; set; } + public StatFilterValue? Block { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/EquipmentFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/EquipmentFilters.cs index 96e907951..73e4d53fe 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/EquipmentFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/EquipmentFilters.cs @@ -5,33 +5,33 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class EquipmentFilters { [JsonPropertyName("ar")] - public SearchFilterValue? Armor { get; set; } + public StatFilterValue? Armor { get; set; } [JsonPropertyName("es")] - public SearchFilterValue? EnergyShield { get; set; } + public StatFilterValue? EnergyShield { get; set; } [JsonPropertyName("ev")] - public SearchFilterValue? Evasion { get; set; } + public StatFilterValue? Evasion { get; set; } [JsonPropertyName("block")] - public SearchFilterValue? Block { get; set; } + public StatFilterValue? Block { get; set; } [JsonPropertyName("crit")] - public SearchFilterValue? CriticalStrikeChance { get; set; } + public StatFilterValue? CriticalStrikeChance { get; set; } [JsonPropertyName("aps")] - public SearchFilterValue? AttacksPerSecond { get; set; } + public StatFilterValue? AttacksPerSecond { get; set; } [JsonPropertyName("dps")] - public SearchFilterValue? DamagePerSecond { get; set; } + public StatFilterValue? DamagePerSecond { get; set; } [JsonPropertyName("edps")] - public SearchFilterValue? ElementalDps { get; set; } + public StatFilterValue? ElementalDps { get; set; } [JsonPropertyName("pdps")] - public SearchFilterValue? PhysicalDps { get; set; } + public StatFilterValue? PhysicalDps { get; set; } [JsonPropertyName("damage")] - public SearchFilterValue? Damage { get; set; } + public StatFilterValue? Damage { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MapFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MapFilters.cs index 57871446e..17b821438 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MapFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MapFilters.cs @@ -5,19 +5,19 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class MapFilters { [JsonPropertyName("map_iiq")] - public SearchFilterValue? ItemQuantity { get; set; } + public StatFilterValue? ItemQuantity { get; set; } [JsonPropertyName("map_iir")] - public SearchFilterValue? ItemRarity { get; set; } + public StatFilterValue? ItemRarity { get; set; } [JsonPropertyName("area_level")] - public SearchFilterValue? AreaLevel { get; set; } + public StatFilterValue? AreaLevel { get; set; } [JsonPropertyName("map_tier")] - public SearchFilterValue? MapTier { get; set; } + public StatFilterValue? MapTier { get; set; } [JsonPropertyName("map_packsize")] - public SearchFilterValue? MonsterPackSize { get; set; } + public StatFilterValue? MonsterPackSize { get; set; } [JsonPropertyName("map_blighted")] public SearchFilterOption? Blighted { get; set; } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MiscFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MiscFilters.cs index fdfa92cb5..bc243bd30 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MiscFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/MiscFilters.cs @@ -4,10 +4,10 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters { internal class MiscFilters { - public SearchFilterValue? Quality { get; set; } + public StatFilterValue? Quality { get; set; } [JsonPropertyName("gem_level")] - public SearchFilterValue? GemLevel { get; set; } + public StatFilterValue? GemLevel { get; set; } [JsonPropertyName("gem_alternate_quality")] public SearchFilterOption? GemQualityType { get; set; } @@ -16,12 +16,12 @@ internal class MiscFilters /// The item level filter for Path of Exile 1 is inside the misc filters instead of the type filters. /// [JsonPropertyName("ilvl")] - public SearchFilterValue? ItemLevel { get; set; } + public StatFilterValue? ItemLevel { get; set; } public SearchFilterOption? Corrupted { get; set; } [JsonPropertyName("scourge_tier")] - public SearchFilterValue? Scourged { get; set; } + public StatFilterValue? Scourged { get; set; } [JsonPropertyName("elder_item")] public SearchFilterOption? ElderItem { get; set; } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/RequirementFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/RequirementFilters.cs index ef04ac26a..aa2c6561f 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/RequirementFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/RequirementFilters.cs @@ -5,15 +5,15 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class RequirementFilters { [JsonPropertyName("lvl")] - public SearchFilterValue? Level { get; set; } + public StatFilterValue? Level { get; set; } [JsonPropertyName("dex")] - public SearchFilterValue? Dexterity { get; set; } + public StatFilterValue? Dexterity { get; set; } [JsonPropertyName("str")] - public SearchFilterValue? Strength { get; set; } + public StatFilterValue? Strength { get; set; } [JsonPropertyName("int")] - public SearchFilterValue? Intelligence { get; set; } + public StatFilterValue? Intelligence { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SocketFilterOption.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SocketFilterOption.cs index fa952b1d3..5a93ef5a8 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SocketFilterOption.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SocketFilterOption.cs @@ -2,7 +2,7 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters { - internal class SocketFilterOption : SearchFilterValue + internal class SocketFilterOption : StatFilterValue { [JsonPropertyName("r")] public int? Red { get; set; } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterGroup.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterGroup.cs index e2ef7aa04..5c1478058 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterGroup.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterGroup.cs @@ -13,5 +13,5 @@ internal class StatFilterGroup public List Filters { get; set; } = new(); - public SearchFilterValue? Value { get; set; } + public StatFilterValue? Value { get; set; } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilterValue.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterValue.cs similarity index 59% rename from src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilterValue.cs rename to src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterValue.cs index cd4ec3a6b..8b7241540 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/SearchFilterValue.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilterValue.cs @@ -2,40 +2,36 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters; -internal class SearchFilterValue +internal class StatFilterValue { - internal SearchFilterValue() + internal StatFilterValue() { } - public SearchFilterValue(string? option) + public StatFilterValue(string? option) { Option = option; } - public SearchFilterValue(PropertyFilter filter) + public StatFilterValue(PropertyFilter filter) { Option = filter.Checked == true ? "true" : "false"; Min = filter.Min; Max = filter.Max; } - public SearchFilterValue(ModifierFilter filter) + public StatFilterValue(ModifierFilter filter) { Option = filter.Line.OptionValue; Min = filter.Min; Max = filter.Max; } - public SearchFilterValue(PseudoModifierFilter filter) - { - Min = filter.Min; - Max = filter.Max; - } - public object? Option { get; set; } public decimal? Min { get; set; } public decimal? Max { get; set; } + + public double? Weight { get; set; } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilters.cs index b4ed7b255..ca05d76cf 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatFilters.cs @@ -3,7 +3,7 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class StatFilters { public string? Id { get; set; } - public SearchFilterValue? Value { get; set; } + public StatFilterValue? Value { get; set; } public bool Disabled => false; } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatType.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatType.cs index daca89b22..e1dce5452 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatType.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/StatType.cs @@ -9,4 +9,7 @@ internal enum StatType [EnumValue("count")] Count, + + [EnumValue("weight2")] + WeightedSum, } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TradeFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TradeFilters.cs index c6b5c5ebb..cf4f1ce9e 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TradeFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TradeFilters.cs @@ -2,6 +2,6 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters { internal class TradeFilters { - public SearchFilterValue? Price { get; set; } + public StatFilterValue? Price { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TypeFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TypeFilters.cs index bba327e25..5711cc0ff 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TypeFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/TypeFilters.cs @@ -12,6 +12,6 @@ internal class TypeFilters /// The item level filter for Path of Exile 2 is inside the type filters instead of the misc filters. /// [JsonPropertyName("ilvl")] - public SearchFilterValue? ItemLevel { get; set; } + public StatFilterValue? ItemLevel { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/WeaponFilters.cs b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/WeaponFilters.cs index f4286c045..19c6db45c 100644 --- a/src/Sidekick.Apis.Poe/Trade/Requests/Filters/WeaponFilters.cs +++ b/src/Sidekick.Apis.Poe/Trade/Requests/Filters/WeaponFilters.cs @@ -5,21 +5,21 @@ namespace Sidekick.Apis.Poe.Trade.Requests.Filters internal class WeaponFilters { [JsonPropertyName("crit")] - public SearchFilterValue? CriticalStrikeChance { get; set; } + public StatFilterValue? CriticalStrikeChance { get; set; } [JsonPropertyName("aps")] - public SearchFilterValue? AttacksPerSecond { get; set; } + public StatFilterValue? AttacksPerSecond { get; set; } [JsonPropertyName("dps")] - public SearchFilterValue? DamagePerSecond { get; set; } + public StatFilterValue? DamagePerSecond { get; set; } [JsonPropertyName("edps")] - public SearchFilterValue? ElementalDps { get; set; } + public StatFilterValue? ElementalDps { get; set; } [JsonPropertyName("pdps")] - public SearchFilterValue? PhysicalDps { get; set; } + public StatFilterValue? PhysicalDps { get; set; } [JsonPropertyName("damage")] - public SearchFilterValue? Damage { get; set; } + public StatFilterValue? Damage { get; set; } } } diff --git a/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs b/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs index 6a21efb02..ac57d5846 100644 --- a/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs +++ b/src/Sidekick.Apis.Poe/Trade/TradeFilterService.cs @@ -37,7 +37,11 @@ public IEnumerable GetPseudoModifierFilters(Item item) foreach (var modifier in item.PseudoModifiers) { - yield return new PseudoModifierFilter(modifier); + yield return new PseudoModifierFilter() + { + PseudoModifier = modifier, + Checked = false, + }; } } diff --git a/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs b/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs index 3a86b23da..664140f9b 100644 --- a/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs +++ b/src/Sidekick.Apis.Poe/Trade/TradeSearchService.cs @@ -102,15 +102,21 @@ public async Task> Search(Item item, PropertyFilters? { Filters = { - Price = new SearchFilterValue(currency), + Price = new StatFilterValue(currency), }, }; } - SetModifierFilters(query.Stats, modifierFilters); - SetPseudoModifierFilters(query.Stats, pseudoFilters); SetSocketFilters(item, query.Filters); + var andGroup = GetAndStats(modifierFilters, pseudoFilters); + if (andGroup != null) query.Stats.Add(andGroup); + + var countGroup = GetCountStats(modifierFilters); + if (countGroup != null) query.Stats.Add(countGroup); + + query.Stats.AddRange(GetWeightedSumStats(pseudoFilters)); + if (propertyFilters != null) { query.Filters.EquipmentFilters = GetEquipmentFilters(item, propertyFilters.Weapon.Concat(propertyFilters.Armour).ToList()); @@ -191,32 +197,32 @@ public async Task> Search(Item item, PropertyFilters? switch (propertyFilter.Type) { case PropertyFilterType.Weapon_Damage: - filters.Filters.Damage = new SearchFilterValue(propertyFilter); + filters.Filters.Damage = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_PhysicalDps: - filters.Filters.PhysicalDps = new SearchFilterValue(propertyFilter); + filters.Filters.PhysicalDps = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_ElementalDps: - filters.Filters.ElementalDps = new SearchFilterValue(propertyFilter); + filters.Filters.ElementalDps = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_Dps: - filters.Filters.DamagePerSecond = new SearchFilterValue(propertyFilter); + filters.Filters.DamagePerSecond = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_AttacksPerSecond: - filters.Filters.AttacksPerSecond = new SearchFilterValue(propertyFilter); + filters.Filters.AttacksPerSecond = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_CriticalStrikeChance: - filters.Filters.CriticalStrikeChance = new SearchFilterValue(propertyFilter); + filters.Filters.CriticalStrikeChance = new StatFilterValue(propertyFilter); hasValue = true; break; } @@ -245,22 +251,22 @@ public async Task> Search(Item item, PropertyFilters? switch (propertyFilter.Type) { case PropertyFilterType.Armour_Armour: - filters.Filters.Armor = new SearchFilterValue(propertyFilter); + filters.Filters.Armor = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_Block: - filters.Filters.Block = new SearchFilterValue(propertyFilter); + filters.Filters.Block = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_EnergyShield: - filters.Filters.EnergyShield = new SearchFilterValue(propertyFilter); + filters.Filters.EnergyShield = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_Evasion: - filters.Filters.Evasion = new SearchFilterValue(propertyFilter); + filters.Filters.Evasion = new StatFilterValue(propertyFilter); hasValue = true; break; } @@ -289,52 +295,52 @@ public async Task> Search(Item item, PropertyFilters? switch (propertyFilter.Type) { case PropertyFilterType.Weapon_Damage: - filters.Filters.Damage = new SearchFilterValue(propertyFilter); + filters.Filters.Damage = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_PhysicalDps: - filters.Filters.PhysicalDps = new SearchFilterValue(propertyFilter); + filters.Filters.PhysicalDps = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_ElementalDps: - filters.Filters.ElementalDps = new SearchFilterValue(propertyFilter); + filters.Filters.ElementalDps = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_Dps: - filters.Filters.DamagePerSecond = new SearchFilterValue(propertyFilter); + filters.Filters.DamagePerSecond = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_AttacksPerSecond: - filters.Filters.AttacksPerSecond = new SearchFilterValue(propertyFilter); + filters.Filters.AttacksPerSecond = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Weapon_CriticalStrikeChance: - filters.Filters.CriticalStrikeChance = new SearchFilterValue(propertyFilter); + filters.Filters.CriticalStrikeChance = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_Armour: - filters.Filters.Armor = new SearchFilterValue(propertyFilter); + filters.Filters.Armor = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_Block: - filters.Filters.Block = new SearchFilterValue(propertyFilter); + filters.Filters.Block = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_EnergyShield: - filters.Filters.EnergyShield = new SearchFilterValue(propertyFilter); + filters.Filters.EnergyShield = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Armour_Evasion: - filters.Filters.Evasion = new SearchFilterValue(propertyFilter); + filters.Filters.Evasion = new StatFilterValue(propertyFilter); hasValue = true; break; } @@ -358,22 +364,22 @@ public async Task> Search(Item item, PropertyFilters? switch (propertyFilter.Type) { case PropertyFilterType.Map_ItemQuantity: - filters.Filters.ItemQuantity = new SearchFilterValue(propertyFilter); + filters.Filters.ItemQuantity = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Map_ItemRarity: - filters.Filters.ItemRarity = new SearchFilterValue(propertyFilter); + filters.Filters.ItemRarity = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Map_AreaLevel: - filters.Filters.AreaLevel = new SearchFilterValue(propertyFilter); + filters.Filters.AreaLevel = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Map_MonsterPackSize: - filters.Filters.MonsterPackSize = new SearchFilterValue(propertyFilter); + filters.Filters.MonsterPackSize = new StatFilterValue(propertyFilter); hasValue = true; break; @@ -388,7 +394,7 @@ public async Task> Search(Item item, PropertyFilters? break; case PropertyFilterType.Map_Tier: - filters.Filters.MapTier = new SearchFilterValue(propertyFilter); + filters.Filters.MapTier = new StatFilterValue(propertyFilter); hasValue = true; break; } @@ -469,30 +475,30 @@ public async Task> Search(Item item, PropertyFilters? // Misc case PropertyFilterType.Misc_Quality: - filters.Filters.Quality = new SearchFilterValue(propertyFilter); + filters.Filters.Quality = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Misc_GemLevel: if (invariantMetadataProvider.UncutGemIds.Contains(item.Metadata.Id)) { - filters.Filters.ItemLevel = new SearchFilterValue(propertyFilter); + filters.Filters.ItemLevel = new StatFilterValue(propertyFilter); } else { - filters.Filters.GemLevel = new SearchFilterValue(propertyFilter); + filters.Filters.GemLevel = new StatFilterValue(propertyFilter); } hasValue = true; break; case PropertyFilterType.Misc_ItemLevel: - filters.Filters.ItemLevel = new SearchFilterValue(propertyFilter); + filters.Filters.ItemLevel = new StatFilterValue(propertyFilter); hasValue = true; break; case PropertyFilterType.Misc_Scourged: - filters.Filters.Scourged = new SearchFilterValue(propertyFilter); + filters.Filters.Scourged = new StatFilterValue(propertyFilter); hasValue = true; break; } @@ -501,116 +507,158 @@ public async Task> Search(Item item, PropertyFilters? return hasValue ? filters : null; } - private static void SetModifierFilters(List stats, List? modifierFilters) + private StatFilterGroup? GetAndStats(List? modifierFilters, List? pseudoFilters) { - if (modifierFilters == null) + var andGroup = new StatFilterGroup() { - return; - } + Type = StatType.And, + }; - var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); - if (andGroup == null) + if (modifierFilters != null) { - andGroup = new StatFilterGroup() + foreach (var filter in modifierFilters) { - Type = StatType.And, - }; - stats.Add(andGroup); - } + if (filter.Checked != true || filter.Line.Modifiers.Count != 1) + { + continue; + } - var countGroup = stats.FirstOrDefault(x => x.Type == StatType.Count); - if (countGroup == null) - { - countGroup = new StatFilterGroup() - { - Type = StatType.Count, - Value = new SearchFilterValue() + andGroup.Filters.Add(new StatFilters() { - Min = 0, - }, - }; - stats.Add(countGroup); + Id = filter.Line.Modifiers.First().Id, + Value = new StatFilterValue(filter), + }); + } } - foreach (var filter in modifierFilters) + if (pseudoFilters != null) { - if (filter.Checked != true) + foreach (var pseudoFilter in pseudoFilters) { - continue; - } - - if (filter.Line.Modifiers.Count == 1) - { - var modifier = filter.Line.Modifiers.FirstOrDefault(); - if (modifier == null) + if (pseudoFilter.Checked != true || string.IsNullOrEmpty(pseudoFilter.PseudoModifier.ModifierId)) { continue; } andGroup.Filters.Add(new StatFilters() { - Id = modifier.Id, - Value = new SearchFilterValue(filter), + Id = pseudoFilter.PseudoModifier.ModifierId, + Value = new StatFilterValue() + { + Min = pseudoFilter.Min, + Max = pseudoFilter.Max, + }, }); - continue; } + } - var modifiers = filter.Line.Modifiers; - if (filter.ForceFirstCategory) - { - modifiers = modifiers.Where(x => x.Category == filter.FirstCategory).ToList(); - } - else if (modifiers.Any(x => x.Category == ModifierCategory.Explicit)) + if (andGroup.Filters.Count == 0) + { + return null; + } + + return andGroup; + } + + private StatFilterGroup? GetCountStats(List? modifierFilters) + { + var countGroup = new StatFilterGroup() + { + Type = StatType.Count, + Value = new StatFilterValue() { - modifiers = modifiers.Where(x => x.Category == ModifierCategory.Explicit).ToList(); - } + Min = 0, + }, + }; - foreach (var modifier in modifiers) + if (modifierFilters != null) + { + foreach (var filter in modifierFilters) { - countGroup.Filters.Add(new StatFilters() + if (filter.Checked != true || filter.Line.Modifiers.Count <= 1) { - Id = modifier.Id, - Value = new SearchFilterValue(filter), - }); - } + continue; + } - if (countGroup.Value != null && modifiers.Any()) - { - countGroup.Value.Min += 1; + var modifiers = filter.Line.Modifiers; + if (filter.ForceFirstCategory) + { + modifiers = modifiers.Where(x => x.Category == filter.FirstCategory).ToList(); + } + else if (modifiers.Any(x => x.Category == ModifierCategory.Explicit)) + { + modifiers = modifiers.Where(x => x.Category == ModifierCategory.Explicit).ToList(); + } + + foreach (var modifier in modifiers) + { + countGroup.Filters.Add(new StatFilters() + { + Id = modifier.Id, + Value = new StatFilterValue(filter), + }); + } + + if (countGroup.Value != null && modifiers.Any()) + { + countGroup.Value.Min += 1; + } } } + + if (countGroup.Filters.Count == 0) + { + return null; + } + + return countGroup; } - private static void SetPseudoModifierFilters(List stats, List? pseudoFilters) + private List GetWeightedSumStats(List? pseudoFilters) { if (pseudoFilters == null) { - return; + return []; } - var andGroup = stats.FirstOrDefault(x => x.Type == StatType.And); - if (andGroup == null) - { - andGroup = new StatFilterGroup() - { - Type = StatType.And, - }; - stats.Add(andGroup); - } + var stats = new List(); foreach (var filter in pseudoFilters) { - if (filter.Checked != true) + if (filter.Checked != true || filter.PseudoModifier.WeightedSumModifiers.Count == 0) { continue; } - andGroup.Filters.Add(new StatFilters() + var group = new StatFilterGroup() { - Id = filter.Modifier.Id, - Value = new SearchFilterValue(filter), - }); + Type = StatType.WeightedSum, + Value = new StatFilterValue() + { + Min = filter.Min, + Max = filter.Max, + }, + }; + + foreach (var modifier in filter.PseudoModifier.WeightedSumModifiers) + { + group.Filters.Add(new StatFilters() + { + Id = modifier.Key, + Value = new StatFilterValue() + { + Weight = modifier.Value, + }, + }); + } + + if (group.Filters.Count > 0) + { + stats.Add(group); + } } + + return stats; } private static void SetSocketFilters(Item item, SearchFilters filters) @@ -640,13 +688,7 @@ public async Task> GetResults(GameType game, string queryId, Lis { logger.LogInformation($"[Trade API] Fetching Trade API Listings from Query {queryId}."); - var pseudo = string.Empty; - if (pseudoFilters?.Count > 0) - { - pseudo = string.Join("", pseudoFilters.Select(x => $"&pseudos[]={x.Modifier.Id}")); - } - - var response = await poeTradeClient.HttpClient.GetAsync(await GetBaseApiUrl(game) + "fetch/" + string.Join(",", ids) + "?query=" + queryId + pseudo); + var response = await poeTradeClient.HttpClient.GetAsync(await GetBaseApiUrl(game) + "fetch/" + string.Join(",", ids) + "?query=" + queryId); if (!response.IsSuccessStatusCode) { return new(); @@ -750,8 +792,6 @@ private TradeItem GetItem(GameType game, Result result) item.ModifierLines.AddRange(ParseModifierLines(result.Item?.ScourgeMods, result.Item?.Extended?.Mods?.Scourge, ParseHash(result.Item?.Extended?.Hashes?.Scourge))); - item.PseudoModifiers.AddRange(ParsePseudoModifiers(result.Item?.PseudoMods, result.Item?.Extended?.Mods?.Pseudo, ParseHash(result.Item?.Extended?.Hashes?.Pseudo))); - item.ModifierLines = item.ModifierLines.OrderBy(x => item.Text.IndexOf(x.Text, StringComparison.InvariantCultureIgnoreCase)).ToList(); return item; @@ -898,34 +938,6 @@ private IEnumerable ParseModifierLines(List? texts, List ParsePseudoModifiers(List? texts, List? mods, List? hashes) - { - if (texts == null || mods == null || hashes == null) - { - yield break; - } - - foreach (var hash in hashes) - { - var id = hash.Value; - if (id == null) - { - continue; - } - - var text = texts.FirstOrDefault(x => modifierProvider.IsMatch(id, x)); - if (text == null) - { - continue; - } - - yield return new PseudoModifier(text: text) - { - Id = id, - }; - } - } - private static IEnumerable ParseSockets(List? sockets) { if (sockets == null) diff --git a/src/Sidekick.Common.Ui/wwwroot/css/app.css b/src/Sidekick.Common.Ui/wwwroot/css/app.css index 30874af5a..431ac92c4 100644 --- a/src/Sidekick.Common.Ui/wwwroot/css/app.css +++ b/src/Sidekick.Common.Ui/wwwroot/css/app.css @@ -3331,10 +3331,6 @@ video { padding-right: 0.5rem; } -.pt-2 { - padding-top: 0.5rem; -} - .pt-\[4px\] { padding-top: 4px; } diff --git a/src/Sidekick.Common/Game/Items/PseudoModifier.cs b/src/Sidekick.Common/Game/Items/PseudoModifier.cs index 2f9697903..fb4bf1d4a 100644 --- a/src/Sidekick.Common/Game/Items/PseudoModifier.cs +++ b/src/Sidekick.Common/Game/Items/PseudoModifier.cs @@ -1,15 +1,15 @@ namespace Sidekick.Common.Game.Items; -public class PseudoModifier(string text) +public class PseudoModifier { - public string? Id { get; init; } - - public string Text { get; set; } = text; - - public double Value { get; set; } + public string? ModifierId { get; init; } /// - /// Gets a value indicating whether this modifier has value. + /// Gets or sets a dictionary of modifiers. The key represents the modifier id from the API. The value represents its weighted sum value. /// - public bool HasValue => Value != 0; + public Dictionary WeightedSumModifiers { get; init; } = []; + + public required string Text { get; set; } + + public double Value { get; set; } } diff --git a/src/Sidekick.Modules.Trade/Components/Filters/PseudoFilterComponent.razor b/src/Sidekick.Modules.Trade/Components/Filters/PseudoFilterComponent.razor index 2cb41e6e5..460e9ed1a 100644 --- a/src/Sidekick.Modules.Trade/Components/Filters/PseudoFilterComponent.razor +++ b/src/Sidekick.Modules.Trade/Components/Filters/PseudoFilterComponent.razor @@ -10,7 +10,7 @@ NoMargin="true" ValueChanged="CheckedChanged"> @Filter.Modifier.Text + Category="ModifierCategory.Pseudo">@Filter.PseudoModifier.Text @@ -19,7 +19,7 @@
- @if ((Filter.Checked ?? false) && Filter.Modifier.HasValue) + @if ((Filter.Checked ?? false) && Filter.PseudoModifier.Value != 0) { } diff --git a/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs index a25d134f4..37e7a954e 100644 --- a/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs +++ b/tests/Sidekick.Apis.Poe.Tests/Poe1/Parser/BodyArmourParsing.cs @@ -58,9 +58,7 @@ can deny that my work has made quite the splash..."" actual.AssertHasModifier(ModifierCategory.Explicit, "#% increased Area Damage", 47); actual.AssertHasModifier(ModifierCategory.Explicit, "Extra gore"); - actual.AssertHasPseudoModifier("+12% total to all Elemental Resistances", 12); actual.AssertHasPseudoModifier("+36% total Elemental Resistance", 36); - actual.AssertHasPseudoModifier("+36% total Resistance", 36); actual.AssertHasPseudoModifier("+55 total maximum Life", 55); }