From bdb6ce4e0d5edc63e6c741d3574394cea4852c8b Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Mon, 25 Dec 2023 22:46:39 +0100 Subject: [PATCH 01/26] Watcher's eye trade search --- src/Classes/TradeQueryGenerator.lua | 54 +- src/Data/QueryMods.lua | 1322 ++++++++++++++++++++++++++- 2 files changed, 1357 insertions(+), 19 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 8acb5fb978..044947d216 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -79,6 +79,7 @@ local tradeStatCategoryIndices = { ["Exarch"] = 3, ["Synthesis"] = 3, ["PassiveNode"] = 2, + ["WatchersEye"] = 2, } local influenceSuffixes = { "_shaper", "_elder", "_adjudicator", "_basilisk", "_crusader", "_eyrie"} @@ -401,6 +402,7 @@ function TradeQueryGeneratorClass:InitMods() ["Exarch"] = { }, ["Synthesis"] = { }, ["PassiveNode"] = { }, + ["WatchersEye"] = { }, } -- originates from: https://www.pathofexile.com/api/trade/data/stats @@ -459,6 +461,17 @@ function TradeQueryGeneratorClass:InitMods() end self:GenerateModData(clusterNotableMods, tradeQueryStatsParsed) + -- Watcher's Eye + local watchersEyeMods = {} + for _,v in pairs(data.uniqueMods["Watcher's Eye"]) do + if v.Id:find("SublimeVision") or v.Id:find("SummonArbalist") then + goto continue + end + watchersEyeMods[v.Id] = v.mod + watchersEyeMods[v.Id].type = "WatchersEye" + ::continue:: + end + self:GenerateModData(watchersEyeMods,tradeQueryStatsParsed,{ ["BaseJewel"] = true, ["AnyJewel"] = true },{["AnyJewel"]="AnyJewel"}) -- Base item implicit mods. A lot of this code is duplicated from generateModData(), but with important small logical flow changes to handle the format differences for baseName, entry in pairs(data.itemBases) do if entry.implicit ~= nil then @@ -757,12 +770,35 @@ function TradeQueryGeneratorClass:StartQuery(slot, options) itemCategoryQueryStr = "jewel.abyss" itemCategory = "AbyssJewel" elseif slot.slotName:find("Jewel") ~= nil then - itemCategoryQueryStr = "jewel" - itemCategory = options.jewelType .. "Jewel" - if itemCategory == "AbyssJewel" then - itemCategoryQueryStr = "jewel.abyss" - elseif itemCategory == "BaseJewel" then - itemCategoryQueryStr = "jewel.base" + if options.jewelType == "Watcher's Eye" then + special={ + queryExtra = { + name = "Watcher's Eye" + }, + queryFilters = { + type_filters = { + filters = { + category = { + option = "jewel" + }, + rarity = { + option = "unique" + } + } + } + }, + watchersEye = true + } + itemCategory = "AnyJewel" + itemCategoryQueryStr = "jewel" + else + itemCategoryQueryStr = "jewel" + itemCategory = options.jewelType .. "Jewel" + if itemCategory == "AbyssJewel" then + itemCategoryQueryStr = "jewel.abyss" + elseif itemCategory == "BaseJewel" then + itemCategoryQueryStr = "jewel.base" + end end elseif slot.slotName:find("Flask") ~= nil then itemCategoryQueryStr = "flask" @@ -820,6 +856,10 @@ function TradeQueryGeneratorClass:ExecuteQuery() self:GeneratePassiveNodeWeights(self.modData.PassiveNode) return end + if self.calcContext.special.watchersEye then + self:GenerateModWeights(self.modData.WatchersEye) + return + end self:GenerateModWeights(self.modData["Explicit"]) self:GenerateModWeights(self.modData["Implicit"]) if self.calcContext.options.includeCorrupted then @@ -1017,7 +1057,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb end if isJewelSlot then - controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, 0, 5, 100, 18, { "Any", "Base", "Abyss" }, function(index, value) end) + controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, 0, 5, 110, 18, { "Any", "Base", "Abyss", "Watcher's Eye" }, function(index, value) end) controls.jewelType.selIndex = self.lastJewelType or 1 controls.jewelTypeLabel = new("LabelControl", {"RIGHT",controls.jewelType,"LEFT"}, -5, 0, 0, 16, "Jewel Type:") diff --git a/src/Data/QueryMods.lua b/src/Data/QueryMods.lua index 14aa763ca6..474fcffee5 100644 --- a/src/Data/QueryMods.lua +++ b/src/Data/QueryMods.lua @@ -2432,7 +2432,7 @@ return { ["specialCaseData"] = { }, ["tradeMod"] = { - ["id"] = "implicit.stat_2524254339", + ["id"] = "implicit.stat_1229298404", ["text"] = "Culling Strike", ["type"] = "implicit", }, @@ -42620,7 +42620,7 @@ return { ["specialCaseData"] = { }, ["tradeMod"] = { - ["id"] = "explicit.stat_2653955271", + ["id"] = "explicit.stat_1123291426", ["text"] = "Damage Penetrates #% Fire Resistance", ["type"] = "explicit", }, @@ -42654,7 +42654,7 @@ return { ["specialCaseData"] = { }, ["tradeMod"] = { - ["id"] = "explicit.stat_2653955271", + ["id"] = "explicit.stat_1123291426", ["text"] = "Damage Penetrates #% Fire Resistance", ["type"] = "explicit", }, @@ -58054,6 +58054,15 @@ return { ["type"] = "implicit", }, }, + ["implicit.stat_1229298404"] = { + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "implicit.stat_1229298404", + ["text"] = "Culling Strike", + ["type"] = "implicit", + }, + }, ["implicit.stat_1263158408"] = { ["1HAxe"] = { ["max"] = 1, @@ -60010,15 +60019,6 @@ return { ["type"] = "implicit", }, }, - ["implicit.stat_2524254339"] = { - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "implicit.stat_2524254339", - ["text"] = "Culling Strike", - ["type"] = "implicit", - }, - }, ["implicit.stat_2530372417"] = { ["1HAxe"] = { ["max"] = 6, @@ -76369,4 +76369,1302 @@ return { }, }, }, + ["WatchersEye"] = { + ["1577_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRate"] = { + ["AnyJewel"] = { + ["max"] = 30, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2731416566", + ["text"] = "#% increased Maximum total Energy Shield Recovery per second from Leech while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["1578_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRateOld"] = { + ["AnyJewel"] = { + ["max"] = 30, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2731416566", + ["text"] = "#% increased Maximum total Energy Shield Recovery per second from Leech while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["4296_HatredAdditionalCriticalStrikeChance"] = { + ["AnyJewel"] = { + ["max"] = 1.8, + ["min"] = 1.2, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2753985507", + ["text"] = "+#% to Critical Strike Chance while affected by Hatred", + ["type"] = "explicit", + }, + }, + ["4322_DeterminationPhysicalDamageReduction"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1873457881", + ["text"] = "#% additional Physical Damage Reduction while affected by Determination", + ["type"] = "explicit", + }, + }, + ["4452_DeterminationAdditionalArmour"] = { + ["AnyJewel"] = { + ["max"] = 1000, + ["min"] = 600, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3742808908", + ["text"] = "+# to Armour while affected by Determination", + ["type"] = "explicit", + }, + }, + ["4540_PrecisionIncreasedAttackDamage"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2048747572", + ["text"] = "#% increased Attack Damage while affected by Precision", + ["type"] = "explicit", + }, + }, + ["4576_PrecisionIncreasedAttackSpeed"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3375743050", + ["text"] = "#% increased Attack Speed while affected by Precision", + ["type"] = "explicit", + }, + }, + ["4688_HatredPhysicalConvertedToCold"] = { + ["AnyJewel"] = { + ["max"] = 40, + ["min"] = 25, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_664849247", + ["text"] = "#% of Physical Damage Converted to Cold Damage while affected by Hatred", + ["type"] = "explicit", + }, + }, + ["4689_AngerPhysicalConvertedToFire"] = { + ["AnyJewel"] = { + ["max"] = 40, + ["min"] = 25, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3624529132", + ["text"] = "#% of Physical Damage Converted to Fire Damage while affected by Anger", + ["type"] = "explicit", + }, + }, + ["4690_WrathPhysicalConvertedToLightning"] = { + ["AnyJewel"] = { + ["max"] = 40, + ["min"] = 25, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2106756686", + ["text"] = "#% of Physical Damage Converted to Lightning Damage while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["4858_GraceBlindEnemiesWhenHit"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2548097895", + ["text"] = "#% chance to Blind Enemies which Hit you while affected by Grace", + ["type"] = "explicit", + }, + }, + ["4866_DeterminationAdditionalBlock"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3692646597", + ["text"] = "+#% Chance to Block Attack Damage while affected by Determination", + ["type"] = "explicit", + }, + }, + ["4923_PrecisionCannotBeBlinded"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1653848515", + ["text"] = "Cannot be Blinded while affected by Precision", + ["type"] = "explicit", + }, + }, + ["4992_ZealotryCastSpeed"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2444534954", + ["text"] = "#% increased Cast Speed while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["5128_DisciplineAdditionalSpellBlock"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1313498929", + ["text"] = "+#% Chance to Block Spell Damage while affected by Discipline", + ["type"] = "explicit", + }, + }, + ["5149_GraceAdditionalChanceToEvade"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_969576725", + ["text"] = "+#% chance to Evade Attack Hits while affected by Grace", + ["type"] = "explicit", + }, + }, + ["5212_PurityOfElementsChaosResistance"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1138813382", + ["text"] = "+#% to Chaos Resistance while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["5279_HatredIncreasedColdDamage"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1413864591", + ["text"] = "#% increased Cold Damage while affected by Hatred", + ["type"] = "explicit", + }, + }, + ["5312_ZealotryConsecratedGroundEnemyDamageTaken"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2434030180", + ["text"] = "Consecrated Ground you create while affected by Zealotry causes enemies to take #% increased Damage", + ["type"] = "explicit", + }, + }, + ["5315_ZealotryConsecratedGroundEffectLingersForMsAfterLeavingTheArea"] = { + ["AnyJewel"] = { + ["max"] = 2, + ["min"] = 2, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2163419452", + ["text"] = "Effects of Consecrated Ground you create while affected by Zealotry Linger for # seconds", + ["type"] = "explicit", + }, + }, + ["5376_ZealotryCriticalStrikeChanceAgainstEnemiesOnConsecratedGround"] = { + ["AnyJewel"] = { + ["max"] = 120, + ["min"] = 100, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_214835567", + ["text"] = "#% increased Critical Strike Chance against Enemies on Consecrated Ground while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["5396_WrathIncreasedCriticalStrikeChance"] = { + ["AnyJewel"] = { + ["max"] = 100, + ["min"] = 70, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3357049845", + ["text"] = "#% increased Critical Strike Chance while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["5422_AngerIncreasedCriticalStrikeMultiplier"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3627458291", + ["text"] = "+#% to Critical Strike Multiplier while affected by Anger", + ["type"] = "explicit", + }, + }, + ["5423_PrecisionIncreasedCriticalStrikeMultiplier"] = { + ["AnyJewel"] = { + ["max"] = 30, + ["min"] = 20, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1817023621", + ["text"] = "+#% to Critical Strike Multiplier while affected by Precision", + ["type"] = "explicit", + }, + }, + ["5436_ZealotryCriticalStrikesPenetratesElementalResistances"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2091518682", + ["text"] = "Critical Strikes Penetrate #% of Enemy Elemental Resistances while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["5538_ClarityDamageTakenFromManaBeforeLife"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 6, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2383304564", + ["text"] = "#% of Damage taken from Mana before Life while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["5549_ClarityDamageTakenGainedAsMana"] = { + ["AnyJewel"] = { + ["max"] = 20, + ["min"] = 15, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_380220671", + ["text"] = "#% of Damage taken while affected by Clarity Recouped as Mana", + ["type"] = "explicit", + }, + }, + ["5588_HasteDebuffsExpireFaster"] = { + ["AnyJewel"] = { + ["max"] = 20, + ["min"] = 15, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_207635700", + ["text"] = "Debuffs on you expire #% faster while affected by Haste", + ["type"] = "explicit", + }, + }, + ["5689_MalevolenceDamageOverTimeMultiplier"] = { + ["AnyJewel"] = { + ["max"] = 22, + ["min"] = 18, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2736708072", + ["text"] = "+#% to Damage over Time Multiplier while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["5759_PurityOfElementsReducedReflectedElementalDamage"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_65331133", + ["text"] = "#% reduced Reflected Elemental Damage taken while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["5838_DisciplineFasterStartOfRecharge"] = { + ["AnyJewel"] = { + ["max"] = 40, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1016185292", + ["text"] = "#% faster start of Energy Shield Recharge while affected by Discipline", + ["type"] = "explicit", + }, + }, + ["5841_DisciplineEnergyShieldPerHit"] = { + ["AnyJewel"] = { + ["max"] = 30, + ["min"] = 20, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3765507527", + ["text"] = "Gain # Energy Shield per Enemy Hit while affected by Discipline", + ["type"] = "explicit", + }, + }, + ["5845_WrathLightningDamageESLeech"] = { + ["AnyJewel"] = { + ["max"] = 1.5, + ["min"] = 1, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_121436064", + ["text"] = "#% of Lightning Damage is Leeched as Energy Shield while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["5858_DisciplineEnergyShieldRecoveryRate"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_80470845", + ["text"] = "#% increased Energy Shield Recovery Rate while affected by Discipline", + ["type"] = "explicit", + }, + }, + ["5864_DisciplineEnergyShieldRegen"] = { + ["AnyJewel"] = { + ["max"] = 2.5, + ["min"] = 1.5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_991194404", + ["text"] = "Regenerate #% of Energy Shield per Second while affected by Discipline", + ["type"] = "explicit", + }, + }, + ["5930_DeterminationReducedExtraDamageFromCrits"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_68410701", + ["text"] = "You take #% reduced Extra Damage from Critical Strikes while affected by Determination", + ["type"] = "explicit", + }, + }, + ["5959_AngerIncreasedFireDamage"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3337107517", + ["text"] = "#% increased Fire Damage while affected by Anger", + ["type"] = "explicit", + }, + }, + ["6024_VitalityLifeRecoveryFromFlasks"] = { + ["AnyJewel"] = { + ["max"] = 70, + ["min"] = 50, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_362838683", + ["text"] = "#% increased Life Recovery from Flasks while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6100_ZealotryGainArcaneSurgeFor4SecondsWhenYouCreateConsecratedGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1919069577", + ["text"] = "Gain Arcane Surge for 4 seconds when you create Consecrated Ground while affected by Zealotry", + ["type"] = "explicit", + }, + }, + ["6158_HasteGainOnslaughtOnKill"] = { + ["AnyJewel"] = { + ["max"] = 4, + ["min"] = 4, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1424006185", + ["text"] = "You gain Onslaught for # seconds on Kill while affected by Haste", + ["type"] = "explicit", + }, + }, + ["6169_HasteGainPhasing"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1346311588", + ["text"] = "You have Phasing while affected by Haste", + ["type"] = "explicit", + }, + }, + ["6534_PurityOfIceImmuneToFreeze"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2720072724", + ["text"] = "Immune to Freeze while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["6537_PurityOfFireImmuneToIgnite"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_371612541", + ["text"] = "Immune to Ignite while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["6541_PurityOfLightningImmuneToShock"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_281949611", + ["text"] = "Immune to Shock while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["6628_MalevolenceLifeAndEnergyShieldRecoveryRate"] = { + ["AnyJewel"] = { + ["max"] = 12, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3643449791", + ["text"] = "#% increased Recovery rate of Life and Energy Shield while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["6637_VitalityLifeGainPerHit"] = { + ["AnyJewel"] = { + ["max"] = 30, + ["min"] = 20, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4259701244", + ["text"] = "Gain # Life per Enemy Hit while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6643_VitalityDamageLifeLeech"] = { + ["AnyJewel"] = { + ["max"] = 1.2, + ["min"] = 0.8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3656959867", + ["text"] = "#% of Damage leeched as Life while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6650_AngerFireDamageLifeLeech"] = { + ["AnyJewel"] = { + ["max"] = 1.5, + ["min"] = 1, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2312747856", + ["text"] = "#% of Fire Damage Leeched as Life while affected by Anger", + ["type"] = "explicit", + }, + }, + ["6673_VitalityLifeRecoveryRate"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2690790844", + ["text"] = "#% increased Life Recovery Rate while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6683_VitalityFlatLifeRegen"] = { + ["AnyJewel"] = { + ["max"] = 140, + ["min"] = 100, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3489570622", + ["text"] = "Regenerate # Life per Second while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6690_VitalityPercentLifeRegen"] = { + ["AnyJewel"] = { + ["max"] = 1.5, + ["min"] = 1, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1165583295", + ["text"] = "Regenerate #% of Life per second while affected by Vitality", + ["type"] = "explicit", + }, + }, + ["6728_WrathIncreasedLightningDamage"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_418293304", + ["text"] = "#% increased Lightning Damage while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["7388_WrathLightningDamageManaLeech"] = { + ["AnyJewel"] = { + ["max"] = 1.5, + ["min"] = 1, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2889601846", + ["text"] = "#% of Lightning Damage is Leeched as Mana while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["7397_ClarityManaRecoveryRate"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_556659145", + ["text"] = "#% increased Mana Recovery Rate while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["8232_ClarityManaAddedAsEnergyShield"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 6, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2831391506", + ["text"] = "Gain #% of Maximum Mana as Extra Maximum Energy Shield while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["8282_HatredAddedColdDamage"] = { + ["AnyJewel"] = { + ["max"] = 87, + ["min"] = 73, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2643562209", + ["text"] = "Adds # to # Cold Damage while affected by Hatred", + ["type"] = "explicit", + }, + }, + ["8416_HasteCooldownRecoveryForMovementSkills"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 30, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3332055899", + ["text"] = "#% increased Cooldown Recovery Rate of Movement Skills used while affected by Haste", + ["type"] = "explicit", + }, + }, + ["8436_GraceIncreasedMovementSpeed"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3329402420", + ["text"] = "#% increased Movement Speed while affected by Grace", + ["type"] = "explicit", + }, + }, + ["8597_AngerPhysicalAddedAsFire"] = { + ["AnyJewel"] = { + ["max"] = 25, + ["min"] = 15, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4245204226", + ["text"] = "Gain #% of Physical Damage as Extra Fire Damage while affected by Anger", + ["type"] = "explicit", + }, + }, + ["8600_WrathPhysicalAddedAsLightning"] = { + ["AnyJewel"] = { + ["max"] = 25, + ["min"] = 15, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2255914633", + ["text"] = "Gain #% of Physical Damage as Extra Lightning Damage while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["8622_PurityOfElementsTakePhysicalAsCold"] = { + ["AnyJewel"] = { + ["max"] = 12, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1710207583", + ["text"] = "#% of Physical Damage from Hits taken as Cold Damage while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["8623_PurityOfIceTakePhysicalAsIce"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 6, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1779027621", + ["text"] = "#% of Physical Damage from Hits taken as Cold Damage while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["8624_PurityOfElementsTakePhysicalAsFire"] = { + ["AnyJewel"] = { + ["max"] = 12, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1722775216", + ["text"] = "#% of Physical Damage from Hits taken as Fire Damage while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["8625_PurityOfFireTakePhysicalAsFire"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 6, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1798459983", + ["text"] = "#% of Physical Damage from Hits taken as Fire Damage while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["8626_PurityOfElementsTakePhysicalAsLightning"] = { + ["AnyJewel"] = { + ["max"] = 12, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_873224517", + ["text"] = "#% of Physical Damage from Hits taken as Lightning Damage while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["8627_PurityOfLightningTakePhysicalAsLightning"] = { + ["AnyJewel"] = { + ["max"] = 10, + ["min"] = 6, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_254131992", + ["text"] = "#% of Physical Damage from Hits taken as Lightning Damage while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["8675_PrideChanceForDoubleDamage"] = { + ["AnyJewel"] = { + ["max"] = 12, + ["min"] = 8, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3371719014", + ["text"] = "#% chance to deal Double Damage while using Pride", + ["type"] = "explicit", + }, + }, + ["8676_PrideChanceToImpale"] = { + ["AnyJewel"] = { + ["max"] = 25, + ["min"] = 25, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4173751044", + ["text"] = "#% chance to Impale Enemies on Hit with Attacks while using Pride", + ["type"] = "explicit", + }, + }, + ["8677_PrideIntimidateOnHit"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3772848194", + ["text"] = "Your Hits Intimidate Enemies for 4 seconds while you are using Pride", + ["type"] = "explicit", + }, + }, + ["8681_PrideIncreasedPhysicalDamage"] = { + ["AnyJewel"] = { + ["max"] = 60, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_576528026", + ["text"] = "#% increased Physical Damage while using Pride", + ["type"] = "explicit", + }, + }, + ["8683_PrideImpaleAdditionalHits"] = { + ["AnyJewel"] = { + ["max"] = 2, + ["min"] = 2, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1011863394", + ["text"] = "Impales you inflict last # additional Hits while using Pride", + ["type"] = "explicit", + }, + }, + ["8778_PrecisionFlaskChargeOnCrit"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3772841281", + ["text"] = "Gain a Flask Charge when you deal a Critical Strike while affected by Precision", + ["type"] = "explicit", + }, + }, + ["8788_ClarityRecoverManaOnSkillUse"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1699077932", + ["text"] = "#% chance to Recover 10% of Mana when you use a Skill while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["8814_HatredColdPenetration"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1222888897", + ["text"] = "Damage Penetrates #% Cold Resistance while affected by Hatred", + ["type"] = "explicit", + }, + }, + ["8816_AngerFirePenetration"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3111519953", + ["text"] = "Damage Penetrates #% Fire Resistance while affected by Anger", + ["type"] = "explicit", + }, + }, + ["8817_WrathLightningPenetration"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1077131949", + ["text"] = "Damage Penetrates #% Lightning Resistance while affected by Wrath", + ["type"] = "explicit", + }, + }, + ["8824_DeterminationReducedReflectedPhysicalDamage"] = { + ["AnyJewel"] = { + ["max"] = 50, + ["min"] = 40, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2457540491", + ["text"] = "#% reduced Reflected Physical Damage taken while affected by Determination", + ["type"] = "explicit", + }, + }, + ["8971_ClarityReducedManaCost"] = { + ["AnyJewel"] = { + ["max"] = 5, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2445618239", + ["text"] = "+# to Total Mana Cost of Skills while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["8976_ClarityReducedManaCostNonChannelled"] = { + ["AnyJewel"] = { + ["max"] = 5, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1853636813", + ["text"] = "Non-Channelling Skills have +# to Total Mana Cost while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["9070_GraceChanceToDodge"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 12, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4071658793", + ["text"] = "+#% chance to Suppress Spell Damage while affected by Grace", + ["type"] = "explicit", + }, + }, + ["9071_HasteChanceToDodgeSpells"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2170859717", + ["text"] = "+#% chance to Suppress Spell Damage while affected by Haste", + ["type"] = "explicit", + }, + }, + ["9316_MalevolenceUnaffectedByBleeding"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4104891138", + ["text"] = "Unaffected by Bleeding while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["9320_PurityOfFireUnaffectedByBurningGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3308185931", + ["text"] = "Unaffected by Burning Ground while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["9325_PurityOfIceUnaffectedByChilledGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2647344903", + ["text"] = "Unaffected by Chilled Ground while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["9326_PurityOfLightningUnaffectedByConductivity"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1567542124", + ["text"] = "Unaffected by Conductivity while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["9331_PurityOfElementsUnaffectedByElementalWeakness"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3223142064", + ["text"] = "Unaffected by Elemental Weakness while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["9332_GraceUnaffectedByEnfeeble"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2365917222", + ["text"] = "Unaffected by Enfeeble while affected by Grace", + ["type"] = "explicit", + }, + }, + ["9333_PurityOfFireUnaffectedByFlammability"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1173690938", + ["text"] = "Unaffected by Flammability while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["9335_PurityOfIceUnaffectedByFrostbite"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4012281889", + ["text"] = "Unaffected by Frostbite while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["9339_MalevolenceUnaffectedByPoison"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_34059570", + ["text"] = "Unaffected by Poison while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["9345_PurityOfLightningUnaffectedByShockedGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2567659895", + ["text"] = "Unaffected by Shocked Ground while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["9347_HasteUnaffectedByTemporalChains"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2806391472", + ["text"] = "Unaffected by Temporal Chains while affected by Haste", + ["type"] = "explicit", + }, + }, + ["9348_DeterminationUnaffectedByVulnerability"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3207781478", + ["text"] = "Unaffected by Vulnerability while affected by Determination", + ["type"] = "explicit", + }, + }, + ["9497_MalevolenceYourAilmentsDealDamageFaster"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3468843137", + ["text"] = "Damaging Ailments you inflict deal Damage #% faster while affected by Malevolence", + ["type"] = "explicit", + }, + }, + }, } \ No newline at end of file From 05c6930a12240df5a20518cb86a8c83f7930ccbe Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Tue, 30 Jan 2024 18:14:33 +0100 Subject: [PATCH 02/26] Improvements per comments - move find button to separate slot instead of using exisitng jewel sockets - use exisitng Watcher's Eye slot(if available) for usage in weight calculations with test item - added option to include rest of applicable mods from Watcher's Eye mod pool that have weight 0 in final query --- src/Classes/TradeQuery.lua | 27 +++++++- src/Classes/TradeQueryGenerator.lua | 99 +++++++++++++++++++---------- src/Launch.lua | 7 ++ src/Modules/ModParser.lua | 22 +++++++ 4 files changed, 121 insertions(+), 34 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 77a9b6a7b6..4a75a5d4ae 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -425,8 +425,14 @@ Highest Weight - Displays the order retrieved from trade]] local row_count = #slotTables + 1 self.slotTables[row_count] = { slotName = "Megalomaniac", unique = true, alreadyCorrupted = true } self:PriceItemRowDisplay(row_count, top_pane_alignment_ref, row_vertical_padding, row_height) + top_pane_alignment_ref[2] = self.controls["name"..row_count] row_count = row_count + 1 - + -- Watcher's Eye + if self:findValidSlotForWatchersEye() then + self.slotTables[row_count] = { slotName = "Watcher's Eye", unique = true } + self:PriceItemRowDisplay(row_count, top_pane_alignment_ref, row_vertical_padding, row_height) + row_count = row_count + 1 + end local effective_row_count = row_count + 2 + 2 -- Two top menu rows, two bottom rows local pane_height = (row_height + row_vertical_padding) * effective_row_count + 2 * pane_margins_vertical + row_height / 2 local pane_width = 850 @@ -784,13 +790,30 @@ function TradeQueryClass:addChaosEquivalentPriceToItems(items) return outputItems end +-- return valid slot for Watcher's Eye +function TradeQueryClass:findValidSlotForWatchersEye() + local tmpWE=nil + for _,v in ipairs(data.uniques.generated) do + if v:find("Watcher's Eye") then + tmpWE= new("Item",v) + break + end + end + for _,v in pairs(self.itemsTab.sockets) do + if not v.inactive then + if self.itemsTab:IsItemValidForSlot(tmpWE,v.slotName,self.itemsTab.activeItemSet) then + return self.itemsTab.sockets[v.nodeId] + end + end + end +end -- Method to generate pane elements for each item slot function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, row_vertical_padding, row_height) local controls = self.controls local slotTbl = self.slotTables[row_idx] local activeSlotRef = slotTbl.nodeId and self.itemsTab.activeItemSet[slotTbl.nodeId] or self.itemsTab.activeItemSet[slotTbl.slotName] - local activeSlot = slotTbl.nodeId and self.itemsTab.sockets[slotTbl.nodeId] or slotTbl.slotName and self.itemsTab.slots[slotTbl.slotName] + local activeSlot = slotTbl.nodeId and self.itemsTab.sockets[slotTbl.nodeId] or slotTbl.slotName and self.itemsTab.slots[slotTbl.slotName] or slotTbl.slotName == "Watcher's Eye" and self:findValidSlotForWatchersEye() local nameColor = slotTbl.unique and colorCodes.UNIQUE or "^7" controls["name"..row_idx] = new("LabelControl", top_pane_alignment_ref, 0, row_height + row_vertical_padding, 100, row_height - 4, nameColor..slotTbl.slotName) controls["bestButton"..row_idx] = new("ButtonControl", { "LEFT", controls["name"..row_idx], "LEFT"}, 100 + 8, 0, 80, row_height, "Find best", function() diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 044947d216..1025f86023 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -686,6 +686,28 @@ function TradeQueryGeneratorClass:StartQuery(slot, options) calcNodesInsteadOfMods = true, } end + if options.special.itemName == "Watcher's Eye" then + special={ + queryExtra = { + name = "Watcher's Eye" + }, + queryFilters = { + type_filters = { + filters = { + category = { + option = "jewel" + }, + rarity = { + option = "unique" + } + } + } + }, + watchersEye = true + } + itemCategory = "AnyJewel" + itemCategoryQueryStr = "jewel" + end elseif slot.slotName == "Weapon 2" or slot.slotName == "Weapon 1" then if existingItem then if existingItem.type == "Shield" then @@ -770,35 +792,12 @@ function TradeQueryGeneratorClass:StartQuery(slot, options) itemCategoryQueryStr = "jewel.abyss" itemCategory = "AbyssJewel" elseif slot.slotName:find("Jewel") ~= nil then - if options.jewelType == "Watcher's Eye" then - special={ - queryExtra = { - name = "Watcher's Eye" - }, - queryFilters = { - type_filters = { - filters = { - category = { - option = "jewel" - }, - rarity = { - option = "unique" - } - } - } - }, - watchersEye = true - } - itemCategory = "AnyJewel" - itemCategoryQueryStr = "jewel" - else - itemCategoryQueryStr = "jewel" - itemCategory = options.jewelType .. "Jewel" - if itemCategory == "AbyssJewel" then - itemCategoryQueryStr = "jewel.abyss" - elseif itemCategory == "BaseJewel" then - itemCategoryQueryStr = "jewel.base" - end + itemCategoryQueryStr = "jewel" + itemCategory = options.jewelType .. "Jewel" + if itemCategory == "AbyssJewel" then + itemCategoryQueryStr = "jewel.abyss" + elseif itemCategory == "BaseJewel" then + itemCategoryQueryStr = "jewel.base" end elseif slot.slotName:find("Flask") ~= nil then itemCategoryQueryStr = "flask" @@ -877,6 +876,28 @@ function TradeQueryGeneratorClass:ExecuteQuery() end end +function TradeQueryGeneratorClass:addMoreWEMods() + local function getTableOfTradeModIds(tbl) + local tmpTable={} + for _,val in ipairs(tbl) do + table.insert(tmpTable,val.tradeModId) + end + return tmpTable + end + for _,skillGroup in ipairs(self.itemsTab.build.skillsTab.socketGroupList) do + for _,gem in ipairs(skillGroup.gemList) do + if gem.gemData.tags.aura and gem.enabled and gem.enableGlobal2 then + local tmpAura=gem.nameSpec:gsub("Vaal ",""):gsub("Impurity","Purity"):gsub("of","Of"):gsub(" ","") + for id,mod in pairs(self.modData.WatchersEye) do + if id:find(tmpAura) and not isValueInTable(getTableOfTradeModIds(self.modWeights),mod.tradeMod.id) then + table.insert(self.modWeights,{invert=false,meanStatDiff=0,weight=0,tradeModId=mod.tradeMod.id}) + end + end + end + end + end +end + function TradeQueryGeneratorClass:FinishQuery() -- Calc original item Stats without anoint or enchant, and use that diff as a basis for default min sum. local originalItem = self.calcContext.slot and self.itemsTab.items[self.calcContext.slot.selItemId] @@ -900,6 +921,10 @@ function TradeQueryGeneratorClass:FinishQuery() local originalOutput = originalItem and self.calcContext.calcFunc({ repSlotName = self.calcContext.slot.slotName, repItem = self.calcContext.testItem }, { nodeAlloc = true }) or self.calcContext.baseOutput local currentStatDiff = TradeQueryGeneratorClass.WeightedRatioOutputs(self.calcContext.baseOutput, originalOutput, self.calcContext.options.statWeights) * 1000 - (self.calcContext.baseStatValue or 0) + if self.calcContext.options.includeAllWEMods then + self:addMoreWEMods() + end + -- Sort by mean Stat diff rather than weight to more accurately prioritize stats that can contribute more table.sort(self.modWeights, function(a, b) return a.meanStatDiff > b.meanStatDiff @@ -1056,14 +1081,14 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb popupHeight = popupHeight + 23 end - if isJewelSlot then - controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, 0, 5, 110, 18, { "Any", "Base", "Abyss", "Watcher's Eye" }, function(index, value) end) + if isJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then + controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, 0, 5, 100, 18, { "Any", "Base", "Abyss" }, function(index, value) end) controls.jewelType.selIndex = self.lastJewelType or 1 controls.jewelTypeLabel = new("LabelControl", {"RIGHT",controls.jewelType,"LEFT"}, -5, 0, 0, 16, "Jewel Type:") lastItemAnchor = controls.jewelType popupHeight = popupHeight + 23 - elseif slot and not isAbyssalJewelSlot then + elseif slot and not isAbyssalJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then controls.influence1 = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, 0, 5, 100, 18, influenceDropdownNames, function(index, value) end) controls.influence1.selIndex = self.lastInfluence1 or 1 controls.influence1Label = new("LabelControl", {"RIGHT",controls.influence1,"LEFT"}, -5, 0, 0, 16, "Influence 1:") @@ -1136,6 +1161,13 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb end popupHeight = popupHeight + 4 + if context.slotTbl.slotName == "Watcher's Eye" then + controls.includeAllWEMods = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, 0, 5, 18, "Include all Watcher's Eye mods:", function(state) end) + controls.includeAllWEMods.tooltipText = "Include all Watcher's Eye mods in the generated query for which weights couldn't be calculated" + lastItemAnchor = controls.includeAllWEMods + popupHeight = popupHeight + 23 + end + controls.generateQuery = new("ButtonControl", { "BOTTOM", nil, "BOTTOM" }, -45, -10, 80, 20, "Execute", function() main:ClosePopup() @@ -1175,6 +1207,9 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb options.maxPrice = tonumber(controls.maxPrice.buf) options.maxPriceType = currencyTable[controls.maxPriceType.selIndex].id end + if controls.includeAllWEMods then + options.includeAllWEMods = controls.includeAllWEMods.state + end options.statWeights = statWeights self:StartQuery(slot, options) diff --git a/src/Launch.lua b/src/Launch.lua index 0775ec4dc2..b8ff97ca20 100644 --- a/src/Launch.lua +++ b/src/Launch.lua @@ -16,6 +16,13 @@ launch = { } SetMainObject(launch) function launch:OnInit() + -- This is the path to emmy_core.dll. The ?.dll at the end is intentional. + package.cpath = package.cpath .. ";C:/Users/borna/.vscode/extensions/tangzx.emmylua-0.5.19/debugger/emmy/windows/x86/?.dll" + local dbg = require("emmy_core") + -- This port must match the Visual Studio Code configuration. Default is 9966. + dbg.tcpListen("localhost", 9966) + -- Uncomment the next line if you want Path of Building to block until the debugger is attached + -- dbg.waitIDE() self.devMode = false self.installedMode = false self.versionNumber = "?" diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 692a44a1f8..ea7ef9432f 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4840,6 +4840,28 @@ local specialModList = { ["nearby allies have (%d+)%% chance to block attack damage per (%d+) strength you have"] = function(block, _, str) return { mod("ExtraAura", "LIST", { onlyAllies = true, mod = mod("BlockChance", "BASE", block) }, { type = "PerStat", stat = "Str", div = tonumber(str) }), } end, + -- Watcher's Eye special cases + ["immune to ignite while affected by purity of fire"] = { flag("IgniteImmune", { type = "Condition", var = "AffectedByPurityofFire" }) }, + ["immune to freeze while affected by purity of ice"] = { flag("FreezeImmune", { type = "Condition", var = "AffectedByPurityofIce" }) }, + ["immune to shock while affected by purity of lightning"] = { flag("ShockImmune", { type = "Condition", var = "AffectedByPurityofLightning" }) }, + ["unaffected by vulnerability while affected by determination"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Vulnerability" }, { type = "Condition", var = "AffectedByDetermination" }) }, + ["unaffected by enfeeble while affected by grace"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Enfeeble" }, { type = "Condition", var = "AffectedByGrace" }) }, + ["unaffected by temporal chains while affected by haste"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Temporal Chains" }, { type = "Condition", var = "AffectedByHaste" }) }, + ["unaffected by bleeding while affected by malevolence"] = { mod("SelfBleedEffect", "MORE", -100, { type = "Condition", var = "AffectedByMalevolence" }) }, + ["unaffected by poison while affected by malevolence"] = { mod("SelfPoisonEffect", "MORE", -100, { type = "Condition", var = "AffectedByMalevolence" }) }, + ["unaffected by elemental weakness while affected by purity of elements"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Elemental Weakness" }, { type = "Condition", var = "AffectedByPurityofElements" }) }, + ["unaffected by flammability while affected by purity of fire"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Flammability" }, { type = "Condition", var = "AffectedByPurityofFire" }) }, + ["unaffected by frostbite while affected by purity of ice"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Frostbite" }, { type = "Condition", var = "AffectedByPurityofIce" }) }, + ["unaffected by conductivity while affected by purity of lightning"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Conductivity" }, { type = "Condition", var = "AffectedByPurityofLightning" }) }, + ["cannot be blinded while affected by precision"] = { flag("Condition:CannotBeBlinded",{type = "Condition", var = "AffectedByPrecision"}) }, + ["your hits intimidate enemies for 4 seconds while you are using pride"] = { mod("EnemyModifier", "LIST", { mod = flag("Condition:Intimidated") }, { type = "Condition", var = "AffectedByPride" }) }, + ["(%d+)%% chance to blind enemies which hit you while affected by grace"] = function(num) return { mod("EnemyModifier","LIST",{mod=flag("Condition:Blinded")},{type="Condition",var="AffectedByGrace"}) } end, + ["debuffs on you expire (%d+)%% faster while affected by haste"] = function(num) return { mod("SelfDebuffExpirationRate", "BASE", num, {type = "Condition", var = "AffectedByHaste"}) } end, + ["(%d+)%% chance to recover 10%% of mana when you use a skill while affected by clarity"] = { mod("","", 0,{type="Condition",var="AffectedByClarity"})}, + ["effects of consecrated ground you create while affected by zealotry linger for 2 seconds"] = { mod("","", 0,{type="Condition",var="AffectedByZealotry"})}, + ["unaffected by shocked ground while affected by purity of lightning"] = { mod("","", 0,{type="Condition",var="AffectedByPurityofLightning"}) }, + ["unaffected by chilled ground while affected by purity of ice"] = { mod("","", 0,{type="Condition",var="AffectedByPurityofIce"}) }, + ["unaffected by burning ground while affected by purity of fire"] = { mod("","", 0,{type="Condition",var="AffectedByPurityofFire"}) }, } for _, name in pairs(data.keystones) do specialModList[name:lower()] = { mod("Keystone", "LIST", name) } From 0dd17a22d4a7fafe8d7df2bc4af5c24479a2f1ae Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Tue, 30 Jan 2024 20:05:54 +0100 Subject: [PATCH 03/26] fix merge mistake --- src/Classes/TradeQuery.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 9e99baba83..56a65bcfeb 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -482,11 +482,6 @@ Highest Weight - Displays the order retrieved from trade]] return scrollBarShown end - - local effective_row_count = row_count + 2 + 2 -- Two top menu rows, two bottom rows - local pane_height = (row_height + row_vertical_padding) * effective_row_count + 2 * pane_margins_vertical + row_height / 2 - local pane_width = 850 - self.controls.fullPrice = new("LabelControl", {"BOTTOM", nil, "BOTTOM"}, 0, -row_height - pane_margins_vertical - row_vertical_padding, pane_width - 2 * pane_margins_horizontal, row_height, "") GlobalCache.useFullDPS = GlobalCache.numActiveSkillInFullDPS > 0 self.controls.close = new("ButtonControl", {"BOTTOM", nil, "BOTTOM"}, 0, -pane_margins_vertical, 90, row_height, "Done", function() From ef32c2c476f72dd68b644080651e240eb5cde4c6 Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Tue, 30 Jan 2024 20:14:29 +0100 Subject: [PATCH 04/26] remove debugger setup -.- --- src/Launch.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Launch.lua b/src/Launch.lua index b8ff97ca20..0775ec4dc2 100644 --- a/src/Launch.lua +++ b/src/Launch.lua @@ -16,13 +16,6 @@ launch = { } SetMainObject(launch) function launch:OnInit() - -- This is the path to emmy_core.dll. The ?.dll at the end is intentional. - package.cpath = package.cpath .. ";C:/Users/borna/.vscode/extensions/tangzx.emmylua-0.5.19/debugger/emmy/windows/x86/?.dll" - local dbg = require("emmy_core") - -- This port must match the Visual Studio Code configuration. Default is 9966. - dbg.tcpListen("localhost", 9966) - -- Uncomment the next line if you want Path of Building to block until the debugger is attached - -- dbg.waitIDE() self.devMode = false self.installedMode = false self.versionNumber = "?" From c2ce9d366b683bd45c7e43e5322b3455dcce35be Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Fri, 2 Feb 2024 20:37:05 +0100 Subject: [PATCH 05/26] Fix getting unweighted mods --- src/Classes/TradeQueryGenerator.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 98e08b87d6..df76c6ff4c 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -887,14 +887,22 @@ function TradeQueryGeneratorClass:addMoreWEMods() end for _,skillGroup in ipairs(self.itemsTab.build.skillsTab.socketGroupList) do for _,gem in ipairs(skillGroup.gemList) do - if gem.gemData.tags.aura and gem.enabled and gem.enableGlobal2 then - local tmpAura=gem.nameSpec:gsub("Vaal ",""):gsub("Impurity","Purity"):gsub("of","Of"):gsub(" ","") - for id,mod in pairs(self.modData.WatchersEye) do - if id:find(tmpAura) and not isValueInTable(getTableOfTradeModIds(self.modWeights),mod.tradeMod.id) then - table.insert(self.modWeights,{invert=false,meanStatDiff=0,weight=0,tradeModId=mod.tradeMod.id}) - end + local tmpAura="" + if not gem.enabled then + goto continue + elseif gem.nameSpec:find("Vaal") and gem.enableGlobal2 then + tmpAura=gem.nameSpec:gsub("Vaal ",""):gsub("Impurity","Purity"):gsub("of","Of"):gsub(" ","") + elseif gem.gemData and gem.gemData.tags.aura or gem.fromItem then + tmpAura=gem.nameSpec:gsub("of","Of"):gsub(" ","") + else + goto continue + end + for id,mod in pairs(self.modData.WatchersEye) do + if id:find(tmpAura) and not isValueInTable(getTableOfTradeModIds(self.modWeights),mod.tradeMod.id) then + table.insert(self.modWeights,{invert=false,meanStatDiff=0,weight=0,tradeModId=mod.tradeMod.id}) end end + ::continue:: end end end From bbb26263d0dd709a985cc18064fc3ab99ca53936 Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Fri, 2 Feb 2024 21:09:43 +0100 Subject: [PATCH 06/26] Actually include corrupted mods --- src/Classes/TradeQueryGenerator.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index df76c6ff4c..e9c6d3e349 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -858,6 +858,9 @@ function TradeQueryGeneratorClass:ExecuteQuery() end if self.calcContext.special.watchersEye then self:GenerateModWeights(self.modData.WatchersEye) + if self.calcContext.options.includeCorrupted then + self:GenerateModWeights(self.modData["Corrupted"]) + end return end self:GenerateModWeights(self.modData["Explicit"]) From abfef518439a833d445ebd82e643b2ed6451aea6 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:20:44 +0200 Subject: [PATCH 07/26] fix checkboxcontrol for watchers eye --- src/Classes/TradeQueryGenerator.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index ee691f00a6..23990ea195 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1290,7 +1290,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb popupHeight = popupHeight + 4 if context.slotTbl.slotName == "Watcher's Eye" then - controls.includeAllWEMods = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, 0, 5, 18, "Include all Watcher's Eye mods:", function(state) end) + controls.includeAllWEMods = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Include all Watcher's Eye mods:", function(state) end) controls.includeAllWEMods.tooltipText = "Include all Watcher's Eye mods in the generated query for which weights couldn't be calculated" lastItemAnchor = controls.includeAllWEMods popupHeight = popupHeight + 23 From 6ede9c8bc3b5b981d3e86cc7523f1533260cba6f Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:31:20 +0200 Subject: [PATCH 08/26] show tooltips for watcher's eye search. either against all jewels or against an equipped eye --- src/Classes/TradeQuery.lua | 30 +- src/Classes/TradeQueryGenerator.lua | 2 +- src/Data/QueryMods.lua | 686 +++++++++++++++------------- 3 files changed, 382 insertions(+), 336 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 606083ca2c..2cbf699f93 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -888,10 +888,8 @@ function TradeQueryClass:findValidSlotForWatchersEye() end end for _,v in pairs(self.itemsTab.sockets) do - if not v.inactive then - if self.itemsTab:IsItemValidForSlot(tmpWE,v.slotName,self.itemsTab.activeItemSet) then - return self.itemsTab.sockets[v.nodeId] - end + if not v.inactive and self.itemsTab:IsItemValidForSlot(tmpWE,v.slotName,self.itemsTab.activeItemSet) then + return self.itemsTab.sockets[v.nodeId] end end end @@ -1042,7 +1040,16 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro local result = self.resultTbl[row_idx][pb_index] local item = new("Item", result.item_string) tooltip:Clear() - self.itemsTab:AddItemTooltip(tooltip, item, slotTbl) + if slotTbl.slotName == "Watcher's Eye" then + local firstValidSlot = self:findValidSlotForWatchersEye() + local currentItem = firstValidSlot.selItemId ~= 0 and self.itemsTab.items[firstValidSlot.selItemId] + local eyeEquipped = currentItem and currentItem.name:find("Watcher's Eye") + -- for watcher's eye we can compare to an already existing one, or + -- default to comparing with all active sockets + self.itemsTab:AddItemTooltip(tooltip, item, eyeEquipped and firstValidSlot or nil) + else + self.itemsTab:AddItemTooltip(tooltip, item, slotTbl) + end addMegalomaniacCompareToTooltipIfApplicable(tooltip, pb_index) tooltip:AddSeparator(10) tooltip:AddLine(16, string.format("^7Price: %s %s", result.amount, result.currency)) @@ -1070,7 +1077,18 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro -- item.baseName is nil and throws error in the following AddItemTooltip func -- if the item is unidentified local item = new("Item", item_string) - self.itemsTab:AddItemTooltip(tooltip, item, slotTbl, true) + -- for watcher's eye we can compare to an already existing one, or + -- default to comparing with all active sockets + if slotTbl.slotName == "Watcher's Eye" then + local firstValidSlot = self:findValidSlotForWatchersEye() + local currentItem = firstValidSlot.selItemId ~= 0 and self.itemsTab.items[firstValidSlot.selItemId] + local eyeEquipped = currentItem and currentItem.name:find("Watcher's Eye") + -- for watcher's eye we can compare to an already existing one, or + -- default to comparing with all active sockets + self.itemsTab:AddItemTooltip(tooltip, item, eyeEquipped and firstValidSlot or nil, true) + else + self.itemsTab:AddItemTooltip(tooltip, item, slotTbl, true) + end addMegalomaniacCompareToTooltipIfApplicable(tooltip, selected_result_index) end end diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 23990ea195..a9b58614c5 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1291,7 +1291,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb if context.slotTbl.slotName == "Watcher's Eye" then controls.includeAllWEMods = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Include all Watcher's Eye mods:", function(state) end) - controls.includeAllWEMods.tooltipText = "Include all Watcher's Eye mods in the generated query for which weights couldn't be calculated" + controls.includeAllWEMods.tooltipText = "Include mods that could not have a weight calculated for them at weight 0." lastItemAnchor = controls.includeAllWEMods popupHeight = popupHeight + 23 end diff --git a/src/Data/QueryMods.lua b/src/Data/QueryMods.lua index 1f787f0ffe..1ec8a7e19c 100644 --- a/src/Data/QueryMods.lua +++ b/src/Data/QueryMods.lua @@ -72578,7 +72578,247 @@ return { }, }, ["WatchersEye"] = { - ["1577_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRate"] = { + ["10058_ClarityReducedManaCost"] = { + ["AnyJewel"] = { + ["max"] = 5, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2445618239", + ["text"] = "+# to Total Mana Cost of Skills while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["10063_ClarityReducedManaCostNonChannelled"] = { + ["AnyJewel"] = { + ["max"] = 5, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1853636813", + ["text"] = "Non-Channelling Skills have +# to Total Mana Cost while affected by Clarity", + ["type"] = "explicit", + }, + }, + ["10173_GraceChanceToDodge"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 12, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4071658793", + ["text"] = "+#% chance to Suppress Spell Damage while affected by Grace", + ["type"] = "explicit", + }, + }, + ["10174_HasteChanceToDodgeSpells"] = { + ["AnyJewel"] = { + ["max"] = 8, + ["min"] = 5, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2170859717", + ["text"] = "+#% chance to Suppress Spell Damage while affected by Haste", + ["type"] = "explicit", + }, + }, + ["10452_MalevolenceUnaffectedByBleeding"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4104891138", + ["text"] = "Unaffected by Bleeding while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["10456_PurityOfFireUnaffectedByBurningGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3308185931", + ["text"] = "Unaffected by Burning Ground while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["10461_PurityOfIceUnaffectedByChilledGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2647344903", + ["text"] = "Unaffected by Chilled Ground while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["10462_PurityOfLightningUnaffectedByConductivity"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1567542124", + ["text"] = "Unaffected by Conductivity while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["10467_PurityOfElementsUnaffectedByElementalWeakness"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3223142064", + ["text"] = "Unaffected by Elemental Weakness while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["10468_GraceUnaffectedByEnfeeble"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2365917222", + ["text"] = "Unaffected by Enfeeble while affected by Grace", + ["type"] = "explicit", + }, + }, + ["10469_PurityOfFireUnaffectedByFlammability"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1173690938", + ["text"] = "Unaffected by Flammability while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["10471_PurityOfIceUnaffectedByFrostbite"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_4012281889", + ["text"] = "Unaffected by Frostbite while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["10475_MalevolenceUnaffectedByPoison"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_34059570", + ["text"] = "Unaffected by Poison while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["10481_PurityOfLightningUnaffectedByShockedGround"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2567659895", + ["text"] = "Unaffected by Shocked Ground while affected by Purity of Lightning", + ["type"] = "explicit", + }, + }, + ["10483_HasteUnaffectedByTemporalChains"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2806391472", + ["text"] = "Unaffected by Temporal Chains while affected by Haste", + ["type"] = "explicit", + }, + }, + ["10484_DeterminationUnaffectedByVulnerability"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3207781478", + ["text"] = "Unaffected by Vulnerability while affected by Determination", + ["type"] = "explicit", + }, + }, + ["10665_MalevolenceYourAilmentsDealDamageFaster"] = { + ["AnyJewel"] = { + ["max"] = 15, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3468843137", + ["text"] = "Damaging Ailments you inflict deal Damage #% faster while affected by Malevolence", + ["type"] = "explicit", + }, + }, + ["12_DeterminationReducedReflectedPhysicalDamage"] = { + ["AnyJewel"] = { + ["max"] = 75, + ["min"] = 50, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2255585376", + ["text"] = "#% of Physical Damage from your Hits cannot be Reflected while affected by Determination", + ["type"] = "explicit", + }, + }, + ["1740_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRate"] = { ["AnyJewel"] = { ["max"] = 30, ["min"] = 30, @@ -72592,7 +72832,7 @@ return { ["type"] = "explicit", }, }, - ["1578_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRateOld"] = { + ["1741_ZealotryMaximumEnergyShieldPerSecondToMaximumEnergyShieldLeechRateOld"] = { ["AnyJewel"] = { ["max"] = 30, ["min"] = 30, @@ -72606,7 +72846,7 @@ return { ["type"] = "explicit", }, }, - ["4296_HatredAdditionalCriticalStrikeChance"] = { + ["4557_HatredAdditionalCriticalStrikeChance"] = { ["AnyJewel"] = { ["max"] = 1.8, ["min"] = 1.2, @@ -72620,7 +72860,21 @@ return { ["type"] = "explicit", }, }, - ["4322_DeterminationPhysicalDamageReduction"] = { + ["4566_PurityOfElementsMaximumElementalResistances"] = { + ["AnyJewel"] = { + ["max"] = 1, + ["min"] = 1, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_3234824465", + ["text"] = "+#% to all maximum Elemental Resistances while affected by Purity of Elements", + ["type"] = "explicit", + }, + }, + ["4584_DeterminationPhysicalDamageReduction"] = { ["AnyJewel"] = { ["max"] = 8, ["min"] = 5, @@ -72634,7 +72888,7 @@ return { ["type"] = "explicit", }, }, - ["4452_DeterminationAdditionalArmour"] = { + ["4770_DeterminationAdditionalArmour"] = { ["AnyJewel"] = { ["max"] = 1000, ["min"] = 600, @@ -72648,7 +72902,7 @@ return { ["type"] = "explicit", }, }, - ["4540_PrecisionIncreasedAttackDamage"] = { + ["4867_PrecisionIncreasedAttackDamage"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -72662,7 +72916,7 @@ return { ["type"] = "explicit", }, }, - ["4576_PrecisionIncreasedAttackSpeed"] = { + ["4909_PrecisionIncreasedAttackSpeed"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -72676,7 +72930,7 @@ return { ["type"] = "explicit", }, }, - ["4688_HatredPhysicalConvertedToCold"] = { + ["5048_HatredPhysicalConvertedToCold"] = { ["AnyJewel"] = { ["max"] = 40, ["min"] = 25, @@ -72690,7 +72944,7 @@ return { ["type"] = "explicit", }, }, - ["4689_AngerPhysicalConvertedToFire"] = { + ["5049_AngerPhysicalConvertedToFire"] = { ["AnyJewel"] = { ["max"] = 40, ["min"] = 25, @@ -72704,7 +72958,7 @@ return { ["type"] = "explicit", }, }, - ["4690_WrathPhysicalConvertedToLightning"] = { + ["5050_WrathPhysicalConvertedToLightning"] = { ["AnyJewel"] = { ["max"] = 40, ["min"] = 25, @@ -72718,7 +72972,7 @@ return { ["type"] = "explicit", }, }, - ["4858_GraceBlindEnemiesWhenHit"] = { + ["5226_GraceBlindEnemiesWhenHit"] = { ["AnyJewel"] = { ["max"] = 50, ["min"] = 30, @@ -72732,7 +72986,7 @@ return { ["type"] = "explicit", }, }, - ["4866_DeterminationAdditionalBlock"] = { + ["5236_DeterminationAdditionalBlock"] = { ["AnyJewel"] = { ["max"] = 8, ["min"] = 5, @@ -72746,7 +73000,7 @@ return { ["type"] = "explicit", }, }, - ["4923_PrecisionCannotBeBlinded"] = { + ["5399_PrecisionCannotBeBlinded"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -72759,7 +73013,7 @@ return { ["type"] = "explicit", }, }, - ["4992_ZealotryCastSpeed"] = { + ["5476_ZealotryCastSpeed"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -72773,7 +73027,7 @@ return { ["type"] = "explicit", }, }, - ["5128_DisciplineAdditionalSpellBlock"] = { + ["5657_DisciplineAdditionalSpellBlock"] = { ["AnyJewel"] = { ["max"] = 8, ["min"] = 5, @@ -72787,7 +73041,7 @@ return { ["type"] = "explicit", }, }, - ["5149_GraceAdditionalChanceToEvade"] = { + ["5680_GraceAdditionalChanceToEvade"] = { ["AnyJewel"] = { ["max"] = 8, ["min"] = 5, @@ -72801,7 +73055,7 @@ return { ["type"] = "explicit", }, }, - ["5212_PurityOfElementsChaosResistance"] = { + ["5749_PurityOfElementsChaosResistance"] = { ["AnyJewel"] = { ["max"] = 50, ["min"] = 30, @@ -72815,7 +73069,21 @@ return { ["type"] = "explicit", }, }, - ["5279_HatredIncreasedColdDamage"] = { + ["5806_PurityOfFireColdAndLightningTakenAsFire"] = { + ["AnyJewel"] = { + ["max"] = 20, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_1723738042", + ["text"] = "#% of Cold and Lightning Damage taken as Fire Damage while affected by Purity of Fire", + ["type"] = "explicit", + }, + }, + ["5819_HatredIncreasedColdDamage"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -72829,7 +73097,7 @@ return { ["type"] = "explicit", }, }, - ["5312_ZealotryConsecratedGroundEnemyDamageTaken"] = { + ["5854_ZealotryConsecratedGroundEnemyDamageTaken"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 8, @@ -72843,7 +73111,7 @@ return { ["type"] = "explicit", }, }, - ["5315_ZealotryConsecratedGroundEffectLingersForMsAfterLeavingTheArea"] = { + ["5857_ZealotryConsecratedGroundEffectLingersForMsAfterLeavingTheArea"] = { ["AnyJewel"] = { ["max"] = 2, ["min"] = 2, @@ -72857,7 +73125,7 @@ return { ["type"] = "explicit", }, }, - ["5376_ZealotryCriticalStrikeChanceAgainstEnemiesOnConsecratedGround"] = { + ["5926_ZealotryCriticalStrikeChanceAgainstEnemiesOnConsecratedGround"] = { ["AnyJewel"] = { ["max"] = 120, ["min"] = 100, @@ -72871,7 +73139,7 @@ return { ["type"] = "explicit", }, }, - ["5396_WrathIncreasedCriticalStrikeChance"] = { + ["5946_WrathIncreasedCriticalStrikeChance"] = { ["AnyJewel"] = { ["max"] = 100, ["min"] = 70, @@ -72885,7 +73153,7 @@ return { ["type"] = "explicit", }, }, - ["5422_AngerIncreasedCriticalStrikeMultiplier"] = { + ["5973_AngerIncreasedCriticalStrikeMultiplier"] = { ["AnyJewel"] = { ["max"] = 50, ["min"] = 30, @@ -72899,7 +73167,7 @@ return { ["type"] = "explicit", }, }, - ["5423_PrecisionIncreasedCriticalStrikeMultiplier"] = { + ["5974_PrecisionIncreasedCriticalStrikeMultiplier"] = { ["AnyJewel"] = { ["max"] = 30, ["min"] = 20, @@ -72913,7 +73181,7 @@ return { ["type"] = "explicit", }, }, - ["5436_ZealotryCriticalStrikesPenetratesElementalResistances"] = { + ["5988_ZealotryCriticalStrikesPenetratesElementalResistances"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 8, @@ -72927,7 +73195,7 @@ return { ["type"] = "explicit", }, }, - ["5538_ClarityDamageTakenFromManaBeforeLife"] = { + ["6093_ClarityDamageTakenFromManaBeforeLife"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 6, @@ -72941,7 +73209,7 @@ return { ["type"] = "explicit", }, }, - ["5549_ClarityDamageTakenGainedAsMana"] = { + ["6111_ClarityDamageTakenGainedAsMana"] = { ["AnyJewel"] = { ["max"] = 20, ["min"] = 15, @@ -72955,7 +73223,7 @@ return { ["type"] = "explicit", }, }, - ["5588_HasteDebuffsExpireFaster"] = { + ["6155_HasteDebuffsExpireFaster"] = { ["AnyJewel"] = { ["max"] = 20, ["min"] = 15, @@ -72969,7 +73237,7 @@ return { ["type"] = "explicit", }, }, - ["5689_MalevolenceDamageOverTimeMultiplier"] = { + ["6264_MalevolenceDamageOverTimeMultiplier"] = { ["AnyJewel"] = { ["max"] = 22, ["min"] = 18, @@ -72983,21 +73251,7 @@ return { ["type"] = "explicit", }, }, - ["5759_PurityOfElementsReducedReflectedElementalDamage"] = { - ["AnyJewel"] = { - ["max"] = 50, - ["min"] = 40, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_65331133", - ["text"] = "#% reduced Reflected Elemental Damage taken while affected by Purity of Elements", - ["type"] = "explicit", - }, - }, - ["5838_DisciplineFasterStartOfRecharge"] = { + ["6434_DisciplineFasterStartOfRecharge"] = { ["AnyJewel"] = { ["max"] = 40, ["min"] = 30, @@ -73011,7 +73265,7 @@ return { ["type"] = "explicit", }, }, - ["5841_DisciplineEnergyShieldPerHit"] = { + ["6437_DisciplineEnergyShieldPerHit"] = { ["AnyJewel"] = { ["max"] = 30, ["min"] = 20, @@ -73025,7 +73279,7 @@ return { ["type"] = "explicit", }, }, - ["5845_WrathLightningDamageESLeech"] = { + ["6442_WrathLightningDamageESLeech"] = { ["AnyJewel"] = { ["max"] = 1.5, ["min"] = 1, @@ -73039,7 +73293,7 @@ return { ["type"] = "explicit", }, }, - ["5858_DisciplineEnergyShieldRecoveryRate"] = { + ["6457_DisciplineEnergyShieldRecoveryRate"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73053,7 +73307,7 @@ return { ["type"] = "explicit", }, }, - ["5864_DisciplineEnergyShieldRegen"] = { + ["6464_DisciplineEnergyShieldRegen"] = { ["AnyJewel"] = { ["max"] = 2.5, ["min"] = 1.5, @@ -73067,7 +73321,7 @@ return { ["type"] = "explicit", }, }, - ["5930_DeterminationReducedExtraDamageFromCrits"] = { + ["6541_DeterminationReducedExtraDamageFromCrits"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -73081,7 +73335,21 @@ return { ["type"] = "explicit", }, }, - ["5959_AngerIncreasedFireDamage"] = { + ["6557_PurityOfIceFireAndLightningTakenAsCold"] = { + ["AnyJewel"] = { + ["max"] = 20, + ["min"] = 10, + }, + ["sign"] = "", + ["specialCaseData"] = { + }, + ["tradeMod"] = { + ["id"] = "explicit.stat_2189467271", + ["text"] = "#% of Fire and Lightning Damage taken as Cold Damage while affected by Purity of Ice", + ["type"] = "explicit", + }, + }, + ["6573_AngerIncreasedFireDamage"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -73095,7 +73363,7 @@ return { ["type"] = "explicit", }, }, - ["6024_VitalityLifeRecoveryFromFlasks"] = { + ["6645_VitalityLifeRecoveryFromFlasks"] = { ["AnyJewel"] = { ["max"] = 70, ["min"] = 50, @@ -73109,7 +73377,7 @@ return { ["type"] = "explicit", }, }, - ["6100_ZealotryGainArcaneSurgeFor4SecondsWhenYouCreateConsecratedGround"] = { + ["6728_ZealotryGainArcaneSurgeFor4SecondsWhenYouCreateConsecratedGround"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73122,7 +73390,7 @@ return { ["type"] = "explicit", }, }, - ["6158_HasteGainOnslaughtOnKill"] = { + ["6792_HasteGainOnslaughtOnKill"] = { ["AnyJewel"] = { ["max"] = 4, ["min"] = 4, @@ -73136,7 +73404,7 @@ return { ["type"] = "explicit", }, }, - ["6169_HasteGainPhasing"] = { + ["6803_HasteGainPhasing"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73149,7 +73417,7 @@ return { ["type"] = "explicit", }, }, - ["6534_PurityOfIceImmuneToFreeze"] = { + ["7235_PurityOfIceImmuneToFreeze"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73162,7 +73430,7 @@ return { ["type"] = "explicit", }, }, - ["6537_PurityOfFireImmuneToIgnite"] = { + ["7238_PurityOfFireImmuneToIgnite"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73175,7 +73443,7 @@ return { ["type"] = "explicit", }, }, - ["6541_PurityOfLightningImmuneToShock"] = { + ["7243_PurityOfLightningImmuneToShock"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73188,7 +73456,7 @@ return { ["type"] = "explicit", }, }, - ["6628_MalevolenceLifeAndEnergyShieldRecoveryRate"] = { + ["7349_MalevolenceLifeAndEnergyShieldRecoveryRate"] = { ["AnyJewel"] = { ["max"] = 12, ["min"] = 8, @@ -73202,7 +73470,7 @@ return { ["type"] = "explicit", }, }, - ["6637_VitalityLifeGainPerHit"] = { + ["7360_VitalityLifeGainPerHit"] = { ["AnyJewel"] = { ["max"] = 30, ["min"] = 20, @@ -73216,7 +73484,7 @@ return { ["type"] = "explicit", }, }, - ["6643_VitalityDamageLifeLeech"] = { + ["7366_VitalityDamageLifeLeech"] = { ["AnyJewel"] = { ["max"] = 1.2, ["min"] = 0.8, @@ -73230,7 +73498,7 @@ return { ["type"] = "explicit", }, }, - ["6650_AngerFireDamageLifeLeech"] = { + ["7373_AngerFireDamageLifeLeech"] = { ["AnyJewel"] = { ["max"] = 1.5, ["min"] = 1, @@ -73244,7 +73512,7 @@ return { ["type"] = "explicit", }, }, - ["6673_VitalityLifeRecoveryRate"] = { + ["7399_VitalityLifeRecoveryRate"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73258,7 +73526,7 @@ return { ["type"] = "explicit", }, }, - ["6683_VitalityFlatLifeRegen"] = { + ["7409_VitalityFlatLifeRegen"] = { ["AnyJewel"] = { ["max"] = 140, ["min"] = 100, @@ -73272,7 +73540,7 @@ return { ["type"] = "explicit", }, }, - ["6690_VitalityPercentLifeRegen"] = { + ["7416_VitalityPercentLifeRegen"] = { ["AnyJewel"] = { ["max"] = 1.5, ["min"] = 1, @@ -73286,7 +73554,7 @@ return { ["type"] = "explicit", }, }, - ["6728_WrathIncreasedLightningDamage"] = { + ["7454_WrathIncreasedLightningDamage"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -73300,7 +73568,7 @@ return { ["type"] = "explicit", }, }, - ["7388_WrathLightningDamageManaLeech"] = { + ["8188_WrathLightningDamageManaLeech"] = { ["AnyJewel"] = { ["max"] = 1.5, ["min"] = 1, @@ -73314,7 +73582,7 @@ return { ["type"] = "explicit", }, }, - ["7397_ClarityManaRecoveryRate"] = { + ["8197_ClarityManaRecoveryRate"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73328,7 +73596,7 @@ return { ["type"] = "explicit", }, }, - ["8232_ClarityManaAddedAsEnergyShield"] = { + ["9174_ClarityManaAddedAsEnergyShield"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 6, @@ -73342,7 +73610,7 @@ return { ["type"] = "explicit", }, }, - ["8282_HatredAddedColdDamage"] = { + ["9237_HatredAddedColdDamage"] = { ["AnyJewel"] = { ["max"] = 87, ["min"] = 73, @@ -73356,7 +73624,7 @@ return { ["type"] = "explicit", }, }, - ["8416_HasteCooldownRecoveryForMovementSkills"] = { + ["9405_HasteCooldownRecoveryForMovementSkills"] = { ["AnyJewel"] = { ["max"] = 50, ["min"] = 30, @@ -73370,7 +73638,7 @@ return { ["type"] = "explicit", }, }, - ["8436_GraceIncreasedMovementSpeed"] = { + ["9428_GraceIncreasedMovementSpeed"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73384,7 +73652,7 @@ return { ["type"] = "explicit", }, }, - ["8597_AngerPhysicalAddedAsFire"] = { + ["9631_AngerPhysicalAddedAsFire"] = { ["AnyJewel"] = { ["max"] = 25, ["min"] = 15, @@ -73398,7 +73666,7 @@ return { ["type"] = "explicit", }, }, - ["8600_WrathPhysicalAddedAsLightning"] = { + ["9634_WrathPhysicalAddedAsLightning"] = { ["AnyJewel"] = { ["max"] = 25, ["min"] = 15, @@ -73412,7 +73680,7 @@ return { ["type"] = "explicit", }, }, - ["8622_PurityOfElementsTakePhysicalAsCold"] = { + ["9656_PurityOfElementsTakePhysicalAsCold"] = { ["AnyJewel"] = { ["max"] = 12, ["min"] = 8, @@ -73426,7 +73694,7 @@ return { ["type"] = "explicit", }, }, - ["8623_PurityOfIceTakePhysicalAsIce"] = { + ["9657_PurityOfIceTakePhysicalAsIce"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 6, @@ -73440,7 +73708,7 @@ return { ["type"] = "explicit", }, }, - ["8624_PurityOfElementsTakePhysicalAsFire"] = { + ["9658_PurityOfElementsTakePhysicalAsFire"] = { ["AnyJewel"] = { ["max"] = 12, ["min"] = 8, @@ -73454,7 +73722,7 @@ return { ["type"] = "explicit", }, }, - ["8625_PurityOfFireTakePhysicalAsFire"] = { + ["9659_PurityOfFireTakePhysicalAsFire"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 6, @@ -73468,7 +73736,7 @@ return { ["type"] = "explicit", }, }, - ["8626_PurityOfElementsTakePhysicalAsLightning"] = { + ["9660_PurityOfElementsTakePhysicalAsLightning"] = { ["AnyJewel"] = { ["max"] = 12, ["min"] = 8, @@ -73482,7 +73750,7 @@ return { ["type"] = "explicit", }, }, - ["8627_PurityOfLightningTakePhysicalAsLightning"] = { + ["9661_PurityOfLightningTakePhysicalAsLightning"] = { ["AnyJewel"] = { ["max"] = 10, ["min"] = 6, @@ -73496,7 +73764,7 @@ return { ["type"] = "explicit", }, }, - ["8675_PrideChanceForDoubleDamage"] = { + ["9710_PrideChanceForDoubleDamage"] = { ["AnyJewel"] = { ["max"] = 12, ["min"] = 8, @@ -73510,7 +73778,7 @@ return { ["type"] = "explicit", }, }, - ["8676_PrideChanceToImpale"] = { + ["9711_PrideChanceToImpale"] = { ["AnyJewel"] = { ["max"] = 25, ["min"] = 25, @@ -73524,7 +73792,7 @@ return { ["type"] = "explicit", }, }, - ["8677_PrideIntimidateOnHit"] = { + ["9712_PrideIntimidateOnHit"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73537,7 +73805,7 @@ return { ["type"] = "explicit", }, }, - ["8681_PrideIncreasedPhysicalDamage"] = { + ["9716_PrideIncreasedPhysicalDamage"] = { ["AnyJewel"] = { ["max"] = 60, ["min"] = 40, @@ -73551,7 +73819,7 @@ return { ["type"] = "explicit", }, }, - ["8683_PrideImpaleAdditionalHits"] = { + ["9718_PrideImpaleAdditionalHits"] = { ["AnyJewel"] = { ["max"] = 2, ["min"] = 2, @@ -73565,7 +73833,7 @@ return { ["type"] = "explicit", }, }, - ["8778_PrecisionFlaskChargeOnCrit"] = { + ["9834_PrecisionFlaskChargeOnCrit"] = { ["AnyJewel"] = { ["max"] = 1, ["min"] = 1, @@ -73578,7 +73846,7 @@ return { ["type"] = "explicit", }, }, - ["8788_ClarityRecoverManaOnSkillUse"] = { + ["9845_ClarityRecoverManaOnSkillUse"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73592,7 +73860,7 @@ return { ["type"] = "explicit", }, }, - ["8814_HatredColdPenetration"] = { + ["9875_HatredColdPenetration"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73606,7 +73874,7 @@ return { ["type"] = "explicit", }, }, - ["8816_AngerFirePenetration"] = { + ["9877_AngerFirePenetration"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73620,7 +73888,7 @@ return { ["type"] = "explicit", }, }, - ["8817_WrathLightningPenetration"] = { + ["9878_WrathLightningPenetration"] = { ["AnyJewel"] = { ["max"] = 15, ["min"] = 10, @@ -73634,245 +73902,5 @@ return { ["type"] = "explicit", }, }, - ["8824_DeterminationReducedReflectedPhysicalDamage"] = { - ["AnyJewel"] = { - ["max"] = 50, - ["min"] = 40, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2457540491", - ["text"] = "#% reduced Reflected Physical Damage taken while affected by Determination", - ["type"] = "explicit", - }, - }, - ["8971_ClarityReducedManaCost"] = { - ["AnyJewel"] = { - ["max"] = 5, - ["min"] = 10, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2445618239", - ["text"] = "+# to Total Mana Cost of Skills while affected by Clarity", - ["type"] = "explicit", - }, - }, - ["8976_ClarityReducedManaCostNonChannelled"] = { - ["AnyJewel"] = { - ["max"] = 5, - ["min"] = 10, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_1853636813", - ["text"] = "Non-Channelling Skills have +# to Total Mana Cost while affected by Clarity", - ["type"] = "explicit", - }, - }, - ["9070_GraceChanceToDodge"] = { - ["AnyJewel"] = { - ["max"] = 15, - ["min"] = 12, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_4071658793", - ["text"] = "+#% chance to Suppress Spell Damage while affected by Grace", - ["type"] = "explicit", - }, - }, - ["9071_HasteChanceToDodgeSpells"] = { - ["AnyJewel"] = { - ["max"] = 8, - ["min"] = 5, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2170859717", - ["text"] = "+#% chance to Suppress Spell Damage while affected by Haste", - ["type"] = "explicit", - }, - }, - ["9316_MalevolenceUnaffectedByBleeding"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_4104891138", - ["text"] = "Unaffected by Bleeding while affected by Malevolence", - ["type"] = "explicit", - }, - }, - ["9320_PurityOfFireUnaffectedByBurningGround"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_3308185931", - ["text"] = "Unaffected by Burning Ground while affected by Purity of Fire", - ["type"] = "explicit", - }, - }, - ["9325_PurityOfIceUnaffectedByChilledGround"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2647344903", - ["text"] = "Unaffected by Chilled Ground while affected by Purity of Ice", - ["type"] = "explicit", - }, - }, - ["9326_PurityOfLightningUnaffectedByConductivity"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_1567542124", - ["text"] = "Unaffected by Conductivity while affected by Purity of Lightning", - ["type"] = "explicit", - }, - }, - ["9331_PurityOfElementsUnaffectedByElementalWeakness"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_3223142064", - ["text"] = "Unaffected by Elemental Weakness while affected by Purity of Elements", - ["type"] = "explicit", - }, - }, - ["9332_GraceUnaffectedByEnfeeble"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2365917222", - ["text"] = "Unaffected by Enfeeble while affected by Grace", - ["type"] = "explicit", - }, - }, - ["9333_PurityOfFireUnaffectedByFlammability"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_1173690938", - ["text"] = "Unaffected by Flammability while affected by Purity of Fire", - ["type"] = "explicit", - }, - }, - ["9335_PurityOfIceUnaffectedByFrostbite"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_4012281889", - ["text"] = "Unaffected by Frostbite while affected by Purity of Ice", - ["type"] = "explicit", - }, - }, - ["9339_MalevolenceUnaffectedByPoison"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_34059570", - ["text"] = "Unaffected by Poison while affected by Malevolence", - ["type"] = "explicit", - }, - }, - ["9345_PurityOfLightningUnaffectedByShockedGround"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2567659895", - ["text"] = "Unaffected by Shocked Ground while affected by Purity of Lightning", - ["type"] = "explicit", - }, - }, - ["9347_HasteUnaffectedByTemporalChains"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_2806391472", - ["text"] = "Unaffected by Temporal Chains while affected by Haste", - ["type"] = "explicit", - }, - }, - ["9348_DeterminationUnaffectedByVulnerability"] = { - ["AnyJewel"] = { - ["max"] = 1, - ["min"] = 1, - }, - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_3207781478", - ["text"] = "Unaffected by Vulnerability while affected by Determination", - ["type"] = "explicit", - }, - }, - ["9497_MalevolenceYourAilmentsDealDamageFaster"] = { - ["AnyJewel"] = { - ["max"] = 15, - ["min"] = 10, - }, - ["sign"] = "", - ["specialCaseData"] = { - }, - ["tradeMod"] = { - ["id"] = "explicit.stat_3468843137", - ["text"] = "Damaging Ailments you inflict deal Damage #% faster while affected by Malevolence", - ["type"] = "explicit", - }, - }, }, } \ No newline at end of file From 61a5ee2442290415232397c970cd37f06bf9e7bf Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:52:36 +0200 Subject: [PATCH 09/26] remove mirrored button from eye and megalomaniac search because it is useless --- src/Classes/TradeQueryGenerator.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index a9b58614c5..f171a104f2 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1192,9 +1192,12 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb options.special = { itemName = context.slotTbl.slotName } end - controls.includeMirrored = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Mirrored items:", function(state) end) - controls.includeMirrored.state = (self.lastIncludeMirrored == nil or self.lastIncludeMirrored == true) - updateLastAnchor(controls.includeMirrored) + -- these unique items cannot be mirrored + if not context.slotTbl.unique then + controls.includeMirrored = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Mirrored items:", function(state) end) + controls.includeMirrored.state = (self.lastIncludeMirrored == nil or self.lastIncludeMirrored == true) + updateLastAnchor(controls.includeMirrored) + end if not isJewelSlot and not isAbyssalJewelSlot and includeScourge then controls.includeScourge = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Scourge Mods:", function(state) end) From ebfe54167951ac9be81411e32c5f3e37ce257e17 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:40:11 +0200 Subject: [PATCH 10/26] fix valid slot for watcher's eye --- src/Classes/TradeQuery.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 2cbf699f93..157a4f92b9 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -879,7 +879,13 @@ function TradeQueryClass:addChaosEquivalentPriceToItems(items) end -- return valid slot for Watcher's Eye +-- Tries to first return an existing watcher's eye slot if possible function TradeQueryClass:findValidSlotForWatchersEye() + for _, socket in pairs(self.itemsTab.sockets) do + if not socket.inactive and self.itemsTab.items[socket.selItemId].name:find("Watcher's Eye") then + return socket + end + end local tmpWE=nil for _,v in ipairs(data.uniques.generated) do if v:find("Watcher's Eye") then From fec4aa7d95d76f83a02218bdc764e3321a042c64 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:45:08 +0200 Subject: [PATCH 11/26] add "include in person" setting --- src/Classes/TradeQuery.lua | 7 ++++++- src/Classes/TradeQueryGenerator.lua | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 157a4f92b9..858a0700f7 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -277,6 +277,11 @@ You can click this button to enter your POESESSID. - You can only generate weighted searches for public leagues. (Generated searches can be modified on trade site to work on other leagues and realms)]] + -- Buyout selection + self.controls.includeInPerson = new("CheckBoxControl", { "TOPRIGHT", self.controls.poesessidButton, "BOTTOMRIGHT" }, + { 0, row_vertical_padding, row_height }, "Include in person:", function(state) end, + "This includes in person offers in the search results.", false) + -- Fetches Box self.maxFetchPerSearchDefault = 2 self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT", nil, "TOPRIGHT"}, {-12, 19, 154, row_height}, "", "Fetch Pages", "%D", 3, function(buf) @@ -449,7 +454,7 @@ Highest Weight - Displays the order retrieved from trade]] t_insert(slotTables, { slotName = self.itemsTab.sockets[nodeId].label, nodeId = nodeId }) end - self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.poesessidButton, "LEFT"}, {0, 0, 0, 0}, "") + self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.poesessidButton, "LEFT"}, {0, row_vertical_padding + row_height, 0, 0}, "") top_pane_alignment_ref = {"TOPLEFT", self.controls.sectionAnchor, "TOPLEFT"} local scrollBarShown = #slotTables > 21 -- clipping starts beyond this -- dynamically hide rows that are above or below the scrollBar diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index f171a104f2..e7b5f326b0 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1016,7 +1016,7 @@ function TradeQueryGeneratorClass:FinishQuery() } } }, - status = { option = "available" }, + status = { option = self.includeInPerson and "available" or "securable" }, stats = { { type = "weight", @@ -1302,6 +1302,10 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb controls.generateQuery = new("ButtonControl", { "BOTTOM", nil, "BOTTOM" }, {-45, -10, 80, 20}, "Execute", function() main:ClosePopup() + if context.controls.includeInPerson then + self.includeInPerson = context.controls.includeInPerson.state + end + if controls.includeMirrored then self.lastIncludeMirrored, options.includeMirrored = controls.includeMirrored.state, controls.includeMirrored.state end From 07429bf3721c86b2e54ca1c726fc3c354b724dde Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 16 Mar 2026 20:48:23 +0200 Subject: [PATCH 12/26] change in person selection to be a dropdown of the ones that are on the trade site --- src/Classes/TradeQuery.lua | 19 +++++++++++++++---- src/Classes/TradeQueryGenerator.lua | 17 ++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 858a0700f7..cfd55afca3 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -278,11 +278,22 @@ You can click this button to enter your POESESSID. on trade site to work on other leagues and realms)]] -- Buyout selection - self.controls.includeInPerson = new("CheckBoxControl", { "TOPRIGHT", self.controls.poesessidButton, "BOTTOMRIGHT" }, - { 0, row_vertical_padding, row_height }, "Include in person:", function(state) end, - "This includes in person offers in the search results.", false) + self.tradeTypes = { + "Instant buyout", + "Instant buyout and in person", + "In person (online in league)", + "In person (online)", + "Any", + } + + self.controls.tradeTypeSelection = new("DropDownControl", { "TOPLEFT", self.controls.poesessidButton, "BOTTOMLEFT" }, + { 0, row_vertical_padding, 188, row_height }, self.tradeTypes, function(index, value) + self.tradeTypeIndex = index + end) + -- remember previous choice + self.controls.tradeTypeSelection:SetSel(self.tradeTypeIndex or 1) --- Fetches Box + -- Fetches Box self.maxFetchPerSearchDefault = 2 self.controls.fetchCountEdit = new("EditControl", {"TOPRIGHT", nil, "TOPRIGHT"}, {-12, 19, 154, row_height}, "", "Fetch Pages", "%D", 3, function(buf) self.maxFetchPages = m_min(m_max(tonumber(buf) or self.maxFetchPerSearchDefault, 1), 10) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index e7b5f326b0..14c05c97dd 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1003,7 +1003,16 @@ function TradeQueryGeneratorClass:FinishQuery() -- This Stat diff value will generally be higher than the weighted sum of the same item, because the stats are all applied at once and can thus multiply off each other. -- So apply a modifier to get a reasonable min and hopefully approximate that the query will start out with small upgrades. local minWeight = megalomaniacSpecialMinWeight or currentStatDiff * 0.5 - + + -- what the trade site API uses for the above + self.tradeTypes = { + "securable", + "available", + "onlineleague", + "online", + "any", + } + local selectedTradeType = self.tradeTypes[self.tradeTypeIndex] -- Generate trade query str and open in browser local filters = 0 local queryTable = { @@ -1016,7 +1025,7 @@ function TradeQueryGeneratorClass:FinishQuery() } } }, - status = { option = self.includeInPerson and "available" or "securable" }, + status = { option = selectedTradeType }, stats = { { type = "weight", @@ -1302,9 +1311,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb controls.generateQuery = new("ButtonControl", { "BOTTOM", nil, "BOTTOM" }, {-45, -10, 80, 20}, "Execute", function() main:ClosePopup() - if context.controls.includeInPerson then - self.includeInPerson = context.controls.includeInPerson.state - end + self.tradeTypeIndex = context.controls.tradeTypeSelection.selIndex if controls.includeMirrored then self.lastIncludeMirrored, options.includeMirrored = controls.includeMirrored.state, controls.includeMirrored.state From 688f27790ba06d19d6636e285095dd1923016f95 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 17 Mar 2026 03:10:40 +0200 Subject: [PATCH 13/26] add button to find exact search result for trade tool --- src/Classes/TradeQuery.lua | 62 +++++++++++++++++++++-------- src/Classes/TradeQueryGenerator.lua | 5 ++- src/Classes/TradeQueryRequests.lua | 5 +++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index cfd55afca3..78584f580e 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -283,7 +283,6 @@ on trade site to work on other leagues and realms)]] "Instant buyout and in person", "In person (online in league)", "In person (online)", - "Any", } self.controls.tradeTypeSelection = new("DropDownControl", { "TOPLEFT", self.controls.poesessidButton, "BOTTOMLEFT" }, @@ -942,6 +941,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro return end context.controls["priceButton"..context.row_idx].label = "Searching..." + self.lastQuery = query self.tradeQueryRequests:SearchWithQueryWeightAdjusted(self.pbRealm, self.pbLeague, query, function(items, errMsg) if errMsg then @@ -1105,8 +1105,6 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro local firstValidSlot = self:findValidSlotForWatchersEye() local currentItem = firstValidSlot.selItemId ~= 0 and self.itemsTab.items[firstValidSlot.selItemId] local eyeEquipped = currentItem and currentItem.name:find("Watcher's Eye") - -- for watcher's eye we can compare to an already existing one, or - -- default to comparing with all active sockets self.itemsTab:AddItemTooltip(tooltip, item, eyeEquipped and firstValidSlot or nil, true) else self.itemsTab:AddItemTooltip(tooltip, item, slotTbl, true) @@ -1118,23 +1116,53 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro return self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].item_string ~= nil end -- Whisper so we can copy to clipboard - controls["whisperButton"..row_idx] = new("ButtonControl", { "TOPLEFT", controls["importButton"..row_idx], "TOPRIGHT"}, {8, 0, 185, row_height}, function() - return self.totalPrice[row_idx] and "Whisper for " .. self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency or "Whisper" - end, function() - Copy(self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].whisper) - end) - controls["whisperButton"..row_idx].enabled = function() - return self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].whisper ~= nil - end - controls["whisperButton"..row_idx].tooltipFunc = function(tooltip) + controls["whisperButton" .. row_idx] = new("ButtonControl", + { "TOPLEFT", controls["importButton" .. row_idx], "TOPRIGHT" }, { 8, 0, 185, row_height }, function() + local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + + if not itemResult then return "" end + + local price = self.totalPrice[row_idx] and + self.totalPrice[row_idx].amount .. " " .. self.totalPrice[row_idx].currency + + if itemResult.whisper then + return price and "Whisper for " .. price or "Whisper" + else + return price and "Search for selected item" + end + + end, function() + local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + if itemResult.whisper then + Copy(itemResult.whisper) + else + local exactQuery = dkjson.decode(self.lastQuery) + -- use trade sum to get the specific item. we could also add the + -- trader name as it is contained in the fetch responses, but + -- this alone doesn't seem to really result in multiple results. + -- trade weight min is exclusive while max is inclusive (makes no sense to me) + exactQuery.query.stats[1].value = { min = tonumber(itemResult.weight) - 0.1, max = tonumber(itemResult.weight) } + + local exactQueryStr = dkjson.encode(exactQuery) + + self.tradeQueryRequests:SearchWithQuery(self.pbRealm, self.pbLeague, exactQueryStr, function(_, _) + end, {callbackQueryId = function(queryId) + local url = self.hostName.."trade/search/"..self.pbLeague.."/"..queryId + Copy(url) + OpenURL(url) + end}) + end + end) + + controls["whisperButton" .. row_idx].tooltipFunc = function(tooltip) tooltip:Clear() - if self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].item_string then - tooltip.center = true - tooltip:AddLine(16, "Copies the item purchase whisper to the clipboard") - end + tooltip.center = true + local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] + local text = itemResult.whisper and "Copies the item purchase whisper to the clipboard" or + "Opens the search page to show the item" + tooltip:AddLine(16, text) end end - -- Method to update the Total Price string sum of all items function TradeQueryClass:GetTotalPriceString() local text = "" diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 14c05c97dd..3959d63cfc 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1010,7 +1010,6 @@ function TradeQueryGeneratorClass:FinishQuery() "available", "onlineleague", "online", - "any", } local selectedTradeType = self.tradeTypes[self.tradeTypeIndex] -- Generate trade query str and open in browser @@ -1113,6 +1112,10 @@ function TradeQueryGeneratorClass:FinishQuery() } end + if options.account then + queryTable.query.filters.trade_filters.filters.account = {input = options.account} + end + if options.maxLevel and options.maxLevel > 0 then queryTable.query.filters.req_filters = { disabled = false, diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index e656fb5657..0df90bae2a 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -285,6 +285,11 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) table.insert(items, { amount = trade_entry.listing.price.amount, currency = trade_entry.listing.price.currency, + -- note: using these to travel to the hideout or for a + -- direct whisper is not allowed, even if they are provided + -- right here + -- hideout_token = trade_entry.listing.hideout_token, + -- whisper_token = trade_entry.listing.whisper_token, item_string = common.base64.decode(trade_entry.item.extended.text), whisper = trade_entry.listing.whisper, weight = trade_entry.item.pseudoMods and trade_entry.item.pseudoMods[1]:match("Sum: (.+)") or "0", From 57978377fe93ea5b22f0207dfdf1aea2b466f33d Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:37:17 +0200 Subject: [PATCH 14/26] trade query result filtering via trader name --- src/Classes/TradeQuery.lua | 17 ++++++++++------- src/Classes/TradeQueryRequests.lua | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 78584f580e..d323753e88 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1117,7 +1117,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end -- Whisper so we can copy to clipboard controls["whisperButton" .. row_idx] = new("ButtonControl", - { "TOPLEFT", controls["importButton" .. row_idx], "TOPRIGHT" }, { 8, 0, 185, row_height }, function() + { "TOPLEFT", controls["importButton" .. row_idx], "TOPRIGHT" }, { 8, 0, 170, row_height }, function() local itemResult = self.itemIndexTbl[row_idx] and self.resultTbl[row_idx][self.itemIndexTbl[row_idx]] if not itemResult then return "" end @@ -1128,7 +1128,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro if itemResult.whisper then return price and "Whisper for " .. price or "Whisper" else - return price and "Search for selected item" + return price and "Search for " .. price or "Search" end end, function() @@ -1137,11 +1137,14 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro Copy(itemResult.whisper) else local exactQuery = dkjson.decode(self.lastQuery) - -- use trade sum to get the specific item. we could also add the - -- trader name as it is contained in the fetch responses, but - -- this alone doesn't seem to really result in multiple results. - -- trade weight min is exclusive while max is inclusive (makes no sense to me) - exactQuery.query.stats[1].value = { min = tonumber(itemResult.weight) - 0.1, max = tonumber(itemResult.weight) } + -- use trade sum to get the specific item. both min and max + -- weight fields seem to be inconsistent. making the minimum + -- e.g. exactly 172.3 as on the result item does not always work + exactQuery.query.stats[1].value = { min = floor(itemResult.weight, 1) - 0.1, max = round(itemResult.weight, 1) + 0.1 } + -- also apply trader name. this should make false positives + -- extremely unlikely. this doesn't seem to take up a filter + -- slot + exactQuery.query.filters.trade_filters = { filters = { account = itemResult.trader } } local exactQueryStr = dkjson.encode(exactQuery) diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 0df90bae2a..180050d106 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -292,6 +292,7 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) -- whisper_token = trade_entry.listing.whisper_token, item_string = common.base64.decode(trade_entry.item.extended.text), whisper = trade_entry.listing.whisper, + trader = trade_entry.listing.account.name, weight = trade_entry.item.pseudoMods and trade_entry.item.pseudoMods[1]:match("Sum: (.+)") or "0", id = trade_entry.id }) From 03665884ada871f4eee8d57a7b099ab714e7d33b Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:10:53 +0200 Subject: [PATCH 15/26] fix trader tool item slot anchor --- src/Classes/TradeQuery.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index d323753e88..7ae9c339c7 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -464,7 +464,7 @@ Highest Weight - Displays the order retrieved from trade]] t_insert(slotTables, { slotName = self.itemsTab.sockets[nodeId].label, nodeId = nodeId }) end - self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.poesessidButton, "LEFT"}, {0, row_vertical_padding + row_height, 0, 0}, "") + self.controls.sectionAnchor = new("LabelControl", {"LEFT", self.controls.tradeTypeSelection, "LEFT"}, {0, row_vertical_padding + row_height, 0, 0}, "") top_pane_alignment_ref = {"TOPLEFT", self.controls.sectionAnchor, "TOPLEFT"} local scrollBarShown = #slotTables > 21 -- clipping starts beyond this -- dynamically hide rows that are above or below the scrollBar From 7d02e1ed69e00e1249e0c97b01208a3bbd0db2f6 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:20:18 +0200 Subject: [PATCH 16/26] improvements to timeless jewel tool: trade type and realm selections --- src/Classes/TreeTab.lua | 289 +++++++++++++++++++++++++--------------- 1 file changed, 181 insertions(+), 108 deletions(-) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index f596fb4daf..6e94836c3a 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -181,7 +181,8 @@ local TreeTabClass = newClass("TreeTab", "ControlHost", function(self, build) end, nil, nil, true) self.controls.treeSearch.tooltipText = "Uses Lua pattern matching for complex searches.\nPrefix your search with \"oil:\" to search by anoint recipe.\nTo search for multiple terms: (increased.fire.damage|increased.area.of.effect|etc)" - self.tradeLeaguesList = { } + -- table holding all realm/league pairs. (allLeagues[realm] = [league.id,...]) + self.tradeLeaguesList = {} -- Find Timeless Jewel Button self.controls.findTimelessJewel = new("ButtonControl", { "LEFT", self.controls.treeSearch, "RIGHT" }, { 8, 0, 150, 20 }, "Find Timeless Jewel", function() self:FindTimelessJewel() @@ -1471,8 +1472,12 @@ function TreeTabClass:FindTimelessJewel() end) controls.devotionSelect2.selIndex = timelessData.devotionVariant2 - controls.jewelSelectLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 25, 0, 16}, "^7Jewel Type:") - controls.jewelSelect = new("DropDownControl", {"LEFT", controls.jewelSelectLabel, "RIGHT"}, {10, 0, 200, 18}, jewelTypes, function(index, value) + local rowSpacing = 6 + local rowHeight = 17 + local labelHeight = 16 + local labelSpacing = 4 + + controls.jewelSelect = new("DropDownControl", {"TOPLEFT", nil, "TOPLEFT"}, {380, 25, 200, rowHeight}, jewelTypes, function(index, value) timelessData.jewelType = value controls.devotionSelectLabel.shown = value.id == 4 -- Militant Faith controls.protectAllocatedLabel.shown = (value.id == 4 and controls.socketFilter.state) @@ -1485,13 +1490,15 @@ function TreeTabClass:FindTimelessJewel() updateSearchList("", true) end) controls.jewelSelect.selIndex = timelessData.jewelType.id + controls.jewelSelectLabel = new("LabelControl", {"RIGHT", controls.jewelSelect, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Jewel Type:") + - controls.conquerorSelectLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 50, 0, 16}, "^7Conqueror:") - controls.conquerorSelect = new("DropDownControl", {"LEFT", controls.conquerorSelectLabel, "RIGHT"}, {10, 0, 200, 18}, conquerorTypes[timelessData.jewelType.id], function(index, value) + controls.conquerorSelect = new("DropDownControl", {"TOPLEFT", controls.jewelSelect, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, conquerorTypes[timelessData.jewelType.id], function(index, value) timelessData.conquerorType = value self.build.modFlag = true end) controls.conquerorSelect.selIndex = timelessData.conquerorType.id + controls.conquerorSelectLabel = new("LabelControl", {"RIGHT", controls.conquerorSelect, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Conqueror:") local allocatedNodes = { } local protectedNodes = { } @@ -1515,8 +1522,8 @@ function TreeTabClass:FindTimelessJewel() self.allocatedNodesInRadiusCount = #nodeNames end - controls.socketSelectLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 75, 0, 16}, "^7Jewel Socket:") - controls.socketSelect = new("TimelessJewelSocketControl", {"LEFT", controls.socketSelectLabel, "RIGHT"}, {10, 0, 200, 18}, jewelSockets, function(index, value) + + controls.socketSelect = new("TimelessJewelSocketControl", {"TOPLEFT", controls.conquerorSelect, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, jewelSockets, function(index, value) timelessData.jewelSocket = value setAllocatedNodes() -- reset list when changing sockets self.build.modFlag = true @@ -1528,6 +1535,7 @@ function TreeTabClass:FindTimelessJewel() break end end + controls.socketSelectLabel = new("LabelControl", {"RIGHT", controls.socketSelect, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Jewel Socket:") local function clearProtected() -- clear all controls, nodes related to Militant Faith filtering protectedNodesCount = 0 @@ -1538,8 +1546,8 @@ function TreeTabClass:FindTimelessJewel() end end end - controls.socketFilterLabel = new("LabelControl", { "TOPRIGHT", nil, "TOPLEFT" }, { 405, 100, 0, 16 }, "^7Filter Nodes:") - controls.socketFilter = new("CheckBoxControl", { "LEFT", controls.socketFilterLabel, "RIGHT" }, { 10, 0, 18 }, nil, function(value) + + controls.socketFilter = new("CheckBoxControl", {"TOPLEFT", controls.socketSelect, "BOTTOMLEFT"}, {0, rowSpacing, rowHeight}, nil, function(value) timelessData.socketFilter = value self.build.modFlag = true controls.socketFilterAdditionalDistanceLabel.shown = value @@ -1553,6 +1561,7 @@ function TreeTabClass:FindTimelessJewel() clearProtected() end end) + controls.socketFilterLabel = new("LabelControl", {"RIGHT", controls.socketFilter, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Filter Nodes:") controls.socketFilter.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() tooltip:AddLine(16, "^7Enable this option to exclude nodes that you do not have allocated on your active passive skill tree.") @@ -1617,11 +1626,11 @@ function TreeTabClass:FindTimelessJewel() local scrollWheelSpeedTbl2 = { ["SHIFT"] = 0.2, ["CTRL"] = 0.002, ["DEFAULT"] = 0.02 } local nodeSliderStatLabel = "None" - controls.nodeSliderLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 125, 0, 16}, "^7Primary Node Weight:") - controls.nodeSlider = new("SliderControl", {"LEFT", controls.nodeSliderLabel, "RIGHT"}, {10, 0, 200, 16}, function(value) + controls.nodeSlider = new("SliderControl", {"TOPLEFT", controls.socketFilter, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) controls.nodeSliderValue.label = s_format("^7%.3f", value * 10) parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) end, scrollWheelSpeedTbl) + controls.nodeSliderLabel = new("LabelControl", {"RIGHT", controls.nodeSlider, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Primary Node Weight:") controls.nodeSlider.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() if not controls.nodeSlider.dragging then @@ -1646,11 +1655,11 @@ function TreeTabClass:FindTimelessJewel() controls.nodeSlider:SetVal(0.1) local nodeSlider2StatLabel = "None" - controls.nodeSlider2Label = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 150, 0, 16}, "^7Secondary Node Weight:") - controls.nodeSlider2 = new("SliderControl", {"LEFT", controls.nodeSlider2Label, "RIGHT"}, {10, 0, 200, 16}, function(value) + controls.nodeSlider2 = new("SliderControl", {"TOPLEFT", controls.nodeSlider, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) controls.nodeSlider2Value.label = s_format("^7%.3f", value * 10) parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) end, scrollWheelSpeedTbl) + controls.nodeSlider2Label = new("LabelControl", {"RIGHT", controls.nodeSlider2, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Secondary Node Weight:") controls.nodeSlider2.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() if not controls.nodeSlider2.dragging then @@ -1674,8 +1683,7 @@ function TreeTabClass:FindTimelessJewel() end controls.nodeSlider2:SetVal(0.1) - controls.nodeSlider3Label = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 175, 0, 16}, "^7Minimum Node Weight:") - controls.nodeSlider3 = new("SliderControl", {"LEFT", controls.nodeSlider3Label, "RIGHT"}, {10, 0, 200, 16}, function(value) + controls.nodeSlider3 = new("SliderControl", {"TOPLEFT", controls.nodeSlider2, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) if value == 1 then controls.nodeSlider3Value.label = "^7Required" else @@ -1683,6 +1691,7 @@ function TreeTabClass:FindTimelessJewel() end parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) end, scrollWheelSpeedTbl2) + controls.nodeSlider3Label = new("LabelControl", {"RIGHT", controls.nodeSlider3, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Minimum Node Weight:") controls.nodeSlider3.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() if not controls.nodeSlider3.dragging then @@ -1728,8 +1737,7 @@ function TreeTabClass:FindTimelessJewel() end buildMods() - controls.nodeSelectLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 200, 0, 16}, "^7Search for Node:") - controls.nodeSelect = new("DropDownControl", {"LEFT", controls.nodeSelectLabel, "RIGHT"}, {10, 0, 200, 18}, modData, function(index, value) + controls.nodeSelect = new("DropDownControl", {"TOPLEFT", controls.nodeSlider3, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, modData, function(index, value) nodeSliderStatLabel = "None" nodeSlider2StatLabel = "None" if value.id then @@ -1788,6 +1796,7 @@ function TreeTabClass:FindTimelessJewel() self.build.modFlag = true end end) + controls.nodeSelectLabel = new("LabelControl", {"RIGHT", controls.nodeSelect, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Search for Node:") controls.nodeSelect.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() if mode ~= "OUT" and value.descriptions then @@ -1962,7 +1971,6 @@ function TreeTabClass:FindTimelessJewel() updateSearchList(newList, true) end - controls.fallbackWeightsLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 225, 0, 16}, "^7Fallback Weight Mode:") local fallbackWeightsList = { } for id, stat in pairs(data.powerStatList) do if not stat.ignoreForItems and stat.label ~= "Name" then @@ -1973,9 +1981,10 @@ function TreeTabClass:FindTimelessJewel() }) end end - controls.fallbackWeightsList = new("DropDownControl", {"LEFT", controls.fallbackWeightsLabel, "RIGHT"}, {10, 0, 200, 18}, fallbackWeightsList, function(index) + controls.fallbackWeightsList = new("DropDownControl", {"TOPLEFT", controls.nodeSelect, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, fallbackWeightsList, function(index) timelessData.fallbackWeightMode.idx = index end) + controls.fallbackWeightsLabel = new("LabelControl", {"RIGHT", controls.fallbackWeightsList, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Fallback Weight Mode:") controls.fallbackWeightsList.selIndex = timelessData.fallbackWeightMode.idx or 1 controls.fallbackWeightsButton = new("ButtonControl", {"LEFT", controls.fallbackWeightsList, "RIGHT"}, {5, 0, 66, 18}, "Generate", function() setupFallbackWeights() @@ -1985,20 +1994,48 @@ function TreeTabClass:FindTimelessJewel() tooltip:Clear() tooltip:AddLine(16, "^7Click this button to generate new fallback node weights, replacing your old ones.") end - controls.totalMinimumWeightLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {405, 250, 0, 16}, "^7Total Minimum Weight:") - controls.totalMinimumWeight = new("EditControl", {"LEFT", controls.totalMinimumWeightLabel, "RIGHT"}, {10, 0, 60, 18}, "", nil, "%D", nil, function(val) + controls.totalMinimumWeight = new("EditControl", {"TOPLEFT", controls.fallbackWeightsList, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, "", nil, "%D", nil, function(val) local num = tonumber(val) timelessData.totalMinimumWeight = num or nil self.build.modFlag = true end) + controls.totalMinimumWeightLabel = new("LabelControl", {"RIGHT", controls.totalMinimumWeight, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Total Minimum Weight:") controls.totalMinimumWeight.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() tooltip:AddLine(16, "^7Optional: Only show results where total weight meets or exceeds this value.") end + local listWidth = 440 + local listHeight = 200 + local buttonHeight = 20 + local edgePadding = 12 + local listYOffset = -(buttonHeight + edgePadding * 2) + controls.searchList = new("EditControl", { "BOTTOMLEFT", nil, "BOTTOMLEFT" }, + { edgePadding, listYOffset, listWidth, listHeight }, timelessData.searchList, nil, + "^%C\t\n", nil, function(value) + timelessData.searchList = value + parseSearchList(0, false) + self.build.modFlag = true + end, 16, true) + controls.searchList.shown = true + controls.searchList.enabled = true + controls.searchList:SetText(timelessData.searchList and timelessData.searchList or "") - controls.searchListButton = new("ButtonControl", {"TOPLEFT", nil, "TOPLEFT"}, {12, 250, 106, 20}, "^7Desired Nodes", function() - if controls.searchListFallback.shown then + controls.searchListFallback = new("EditControl", { "BOTTOMLEFT", nil, "BOTTOMLEFT" }, + { edgePadding, listYOffset, listWidth, listHeight }, + timelessData.searchListFallback, nil, "^%C\t\n", nil, function(value) + timelessData.searchListFallback = value + parseSearchList(0, true) + self.build.modFlag = true + end, 16, true) + controls.searchListFallback.shown = false + controls.searchListFallback.enabled = false + controls.searchListFallback:SetText(timelessData.searchListFallback and timelessData.searchListFallback or "") + + controls.searchListButton = new("ButtonControl", + { "BOTTOMLEFT", nil, "BOTTOMLEFT" }, + { edgePadding, listYOffset - listHeight - rowSpacing, 106, buttonHeight }, "^7Desired Nodes", function() + if controls.searchListFallback.shown then controls.searchListFallback.shown = false controls.searchListFallback.enabled = false controls.searchList.shown = true @@ -2007,11 +2044,14 @@ function TreeTabClass:FindTimelessJewel() end) controls.searchListButton.tooltipFunc = function(tooltip, mode, index, value) tooltip:Clear() - tooltip:AddLine(16, "^7This contains a list of your desired nodes along with their primary, secondary, and minimum weights.") - tooltip:AddLine(16, "^7This list can be updated manually or by selecting the node you want to update via the search dropdown list and then moving the node weight sliders.") + tooltip:AddLine(16, + "^7This contains a list of your desired nodes along with their primary, secondary, and minimum weights.") + tooltip:AddLine(16, + "^7This list can be updated manually or by selecting the node you want to update via the search dropdown list and then moving the node weight sliders.") end controls.searchListButton.locked = function() return controls.searchList.shown end - controls.searchListFallbackButton = new("ButtonControl", {"LEFT", controls.searchListButton, "RIGHT"}, {5, 0, 110, 20}, "^7Fallback Nodes", function() + + controls.searchListFallbackButton = new("ButtonControl", {"LEFT", controls.searchListButton, "RIGHT"}, {5, 0, 110, buttonHeight}, "^7Fallback Nodes", function() controls.searchList.shown = false controls.searchList.enabled = false controls.searchListFallback.shown = true @@ -2027,68 +2067,12 @@ function TreeTabClass:FindTimelessJewel() tooltip:AddLine(16, "^7Any manual changes made to your fallback nodes are lost when you click the generate button, as it completely replaces them.") end controls.searchListFallbackButton.locked = function() return controls.searchListFallback.shown end - controls.searchList = new("EditControl", {"TOPLEFT", nil, "TOPLEFT"}, {12, 275, 438, 200}, timelessData.searchList, nil, "^%C\t\n", nil, function(value) - timelessData.searchList = value - parseSearchList(0, false) - self.build.modFlag = true - end, 16, true) - controls.searchList.shown = true - controls.searchList.enabled = true - controls.searchList:SetText(timelessData.searchList and timelessData.searchList or "") - controls.searchListFallback = new("EditControl", {"TOPLEFT", nil, "TOPLEFT"}, {12, 275, 438, 200}, timelessData.searchListFallback, nil, "^%C\t\n", nil, function(value) - timelessData.searchListFallback = value - parseSearchList(0, true) - self.build.modFlag = true - end, 16, true) - controls.searchListFallback.shown = false - controls.searchListFallback.enabled = false - controls.searchListFallback:SetText(timelessData.searchListFallback and timelessData.searchListFallback or "") - controls.searchResultsLabel = new("LabelControl", { "TOPLEFT", nil, "TOPRIGHT" }, { -390, 250, 0, 16 }, "^7Results:") - controls.searchResults = new("TimelessJewelListControl", { "TOPLEFT", nil, "TOPRIGHT" }, { -450, 275, 438, 200 }, self.build) - controls.searchTradeLeagueSelect = new("DropDownControl", { "BOTTOMRIGHT", controls.searchResults, "TOPRIGHT" }, { -175, -5, 140, 20 }, nil, function(_, value) - self.timelessJewelLeagueSelect = value - end) + controls.searchResults = new("TimelessJewelListControl", { "BOTTOMLEFT", nil, "BOTTOMLEFT" }, + { edgePadding*2 + listWidth, -(buttonHeight + edgePadding * 2), listWidth, listHeight }, self.build) self.tradeQueryRequests = new("TradeQueryRequests") - controls.msg = new("LabelControl", nil, { -280, 5, 0, 16 }, "") - if #self.tradeLeaguesList > 0 then - controls.searchTradeLeagueSelect:SetList(self.tradeLeaguesList) - -- restore the last league selected - for i, league in ipairs(self.tradeLeaguesList) do - if league == self.timelessJewelLeagueSelect then - controls.searchTradeLeagueSelect:SetSel(i) - break - end - end - else - self.tradeQueryRequests:FetchLeagues("pc", function(leagues, errMsg) - if errMsg then - controls.msg.label = "^1Error fetching league list, default league will be used\n"..errMsg.."^7" - return - end - local tempLeagueTable = { } - for _, league in ipairs(leagues) do - if league ~= "Standard" and league ~= "Ruthless" and league ~= "Hardcore" and league ~= "Hardcore Ruthless" then - if not (league:find("Hardcore") or league:find("Ruthless")) then - -- set the dynamic, base league name to index 1 to sync league shown in dropdown on load with default/old behavior of copy trade url - t_insert(tempLeagueTable, league) - for _, val in ipairs(self.tradeLeaguesList) do - t_insert(tempLeagueTable, val) - end - self.tradeLeaguesList = copyTable(tempLeagueTable) - else - t_insert(self.tradeLeaguesList, league) - end - end - end - t_insert(self.tradeLeaguesList, "Standard") - t_insert(self.tradeLeaguesList, "Hardcore") - t_insert(self.tradeLeaguesList, "Ruthless") - t_insert(self.tradeLeaguesList, "Hardcore Ruthless") - controls.searchTradeLeagueSelect:SetList(self.tradeLeaguesList) - end) - end - controls.searchTradeButton = new("ButtonControl", { "BOTTOMRIGHT", controls.searchResults, "TOPRIGHT" }, { 0, -5, 170, 20 }, "Copy Trade URL", function() + controls.msg = new("LabelControl", nil, { -280, 5, 0, 20 }, "") + controls.searchTradeButton = new("ButtonControl", { "BOTTOMRIGHT", controls.searchResults, "TOPRIGHT" }, { 0, -rowSpacing, 170, buttonHeight }, "Copy Trade URL", function() local seedTrades = {} local startRow = controls.searchResults.selIndex or 1 local endRow = startRow + m_floor(10 / ((timelessData.sharedResults.conqueror.id == 1) and 3 or 1)) @@ -2134,10 +2118,17 @@ function TreeTabClass:FindTimelessJewel() end end + local tradeTypes = { + "securable", + "available", + "onlineleague", + "online", + "any" + } local search = { query = { status = { - option = "available" + option = tradeTypes[self.tradeTypeIndex] }, stats = { { @@ -2171,10 +2162,12 @@ function TreeTabClass:FindTimelessJewel() end -- if the league was not selected via dropdown, then default to the first league in the dropdown or "" if the leagues could not be read - self.timelessJewelLeagueSelect = self.timelessJewelLeagueSelect or (self.tradeLeaguesList and #self.tradeLeaguesList > 0 and self.tradeLeaguesList[1]) or "" + local selectedRealm = controls.realmSelection:GetSelValue():lower() - Copy("https://www.pathofexile.com/trade/search/"..(self.timelessJewelLeagueSelect).."/?q=" .. (s_gsub(dkjson.encode(search), "[^a-zA-Z0-9]", function(a) - return s_format("%%%02X", s_byte(a)) + local realmPath = selectedRealm == "pc" and "" or (selectedRealm .. "/") + Copy("https://www.pathofexile.com/trade/search/" .. realmPath .. + (controls.searchTradeLeagueSelect:GetSelValue()) .. "/?q=" .. (s_gsub(dkjson.encode(search), "[^a-zA-Z0-9]", function(a) + return s_format("%%%02X", s_byte(a)) end))) controls.searchTradeButton.label = "Copy Next Trade URL" @@ -2189,12 +2182,87 @@ function TreeTabClass:FindTimelessJewel() tooltip:AddLine(16, "^7After selecting a row You can also shift+click on another row to select a range of rows to search.") end - local width = 80 - local divider = 10 - local buttons = 3 - local totalWidth = m_floor(width * buttons + divider * (buttons - 1)) - local buttonX = -totalWidth / 2 + width / 2 - + controls.searchTradeLeagueSelect = new("DropDownControl", { "RIGHT", controls.searchTradeButton, "LEFT" }, + { -labelSpacing, 0, 140, buttonHeight }, nil, function(idx, val) + self.timelessJewelLeagueSelect = val + end) + controls.searchTradeLeagueLabel = new("LabelControl", { "TOPRIGHT", controls.searchTradeLeagueSelect, "TOPLEFT" }, + { -labelSpacing, 0, 0, labelHeight }, "^7League:") + -- Realm selection + self.realmList = { + "PC", "Sony", "Xbox" + } + controls.realmSelection = new("DropDownControl", { "BOTTOMLEFT", controls.searchTradeLeagueSelect, "TOPLEFT" }, + { 0, -rowSpacing, 80, buttonHeight }, self.realmList, nil) + local function updateLeagues() + local currentRealmId = controls.realmSelection:GetSelValue():lower() + if self.tradeLeaguesList[currentRealmId] == nil then self.tradeLeaguesList[currentRealmId] = {} end + local leagueList = self.tradeLeaguesList[currentRealmId] + if leagueList and #leagueList > 0 then + controls.searchTradeLeagueSelect:SetList(leagueList) + -- restore the last league selected + for i, league in ipairs(leagueList) do + if league == self.timelessJewelLeagueSelect then + controls.searchTradeLeagueSelect:SetSel(i) + break + end + end + else + self.tradeQueryRequests:FetchLeagues(currentRealmId, function(leagues, errMsg) + if errMsg then + controls.msg.label = "^1Error fetching league list, default league will be used\n" .. errMsg .. "^7" + return + end + local tempLeagueTable = {} + for _, league in ipairs(leagues) do + if league ~= "Standard" and league ~= "Ruthless" and league ~= "Hardcore" and league ~= "Hardcore Ruthless" then + if not (league:find("Hardcore") or league:find("Ruthless")) then + -- set the dynamic, base league name to index 1 to sync league shown in dropdown on load with default/old behavior of copy trade url + t_insert(tempLeagueTable, league) + for _, val in ipairs(leagueList) do + t_insert(tempLeagueTable, val) + end + leagueList = copyTable(tempLeagueTable) + else + t_insert(leagueList, league) + end + end + end + t_insert(leagueList, "Standard") + t_insert(leagueList, "Hardcore") + t_insert(leagueList, "Ruthless") + t_insert(leagueList, "Hardcore Ruthless") + controls.searchTradeLeagueSelect:SetList(leagueList) + end) + end + end + controls.realmSelection.selFunc = function(idx, _) + self.selectedRealmIndex = idx + updateLeagues() + end + -- remember previous choice + controls.realmSelection:SetSel(self.selectedRealmIndex or 1) + -- manually call the function because when initialising, because the + -- function doesnt get called when the selection index doesnt change + controls.realmSelection.selFunc(controls.realmSelection.selIndex) + controls.realmLabel = new("LabelControl", { "TOPRIGHT", controls.realmSelection, "TOPLEFT" }, + { -labelSpacing, 0, 0, labelHeight }, "^7Realm:") + + -- Buyout selection + local tradeTypes = { + "Instant buyout", + "Instant buyout and in person", + "In person (online in league)", + "In person (online)", + "Any (includes offline)" + } + controls.tradeTypeSelection = new("DropDownControl", { "LEFT", controls.realmSelection, "RIGHT" }, + { labelSpacing, 0, 205, buttonHeight }, tradeTypes, function(index, value) + self.tradeTypeIndex = index + end) + -- remember previous choice + self.tradeTypeIndex = self.tradeTypeIndex or 1 + controls.tradeTypeSelection:SetSel(self.tradeTypeIndex) -- Helper function to search a single socket local function searchSingleSocket(socketId, socketInfo) if not treeData.nodes[socketId] or not treeData.nodes[socketId].isJewelSocket then @@ -2510,7 +2578,21 @@ function TreeTabClass:FindTimelessJewel() return results end - controls.searchButton = new("ButtonControl", nil, {buttonX, 485, width, 20}, "Search", function() + local panelWidth = edgePadding * 3 + listWidth * 2 + local buttonDivider = 10 + local buttonWidth = 80 + -- reset button anchored to middle of panel and other buttons anchored to it + controls.resetButton = new("ButtonControl", {"BOTTOMLEFT", nil, "BOTTOMLEFT"}, {panelWidth / 2 - buttonWidth/2, -edgePadding, buttonWidth, buttonHeight}, "Reset", function() + updateSearchList("", true) + updateSearchList("", false) + wipeTable(timelessData.searchResults) + controls.searchTradeButton.enabled = false + clearProtected() + end) + controls.closeButton = new("ButtonControl", {"LEFT", controls.resetButton, "RIGHT"}, {buttonDivider, 0, buttonWidth, buttonHeight}, "Cancel", function() + main:ClosePopup() + end) + controls.searchButton = new("ButtonControl", {"RIGHT", controls.resetButton, "LEFT"}, {-buttonDivider, 0, buttonWidth, buttonHeight}, "Search", function() if timelessData.jewelSocket.id == -1 then wipeTable(timelessData.searchResults) wipeTable(timelessData.sharedResults) @@ -2576,16 +2658,7 @@ function TreeTabClass:FindTimelessJewel() end end end) - controls.resetButton = new("ButtonControl", nil, {buttonX + (width + divider), 485, width, 20}, "Reset", function() - updateSearchList("", true) - updateSearchList("", false) - wipeTable(timelessData.searchResults) - controls.searchTradeButton.enabled = false - clearProtected() - end) - controls.closeButton = new("ButtonControl", nil, {buttonX + (width + divider) * 2, 485, width, 20}, "Cancel", function() - main:ClosePopup() - end) - main:OpenPopup(910, 517, "Find a Timeless Jewel", controls) + local panelHeight = 565 + main:OpenPopup(panelWidth, panelHeight, "Find a Timeless Jewel", controls) end From 688515e2a7f152717ee2806ae57a6ff373031949 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:30:59 +0300 Subject: [PATCH 17/26] trader tool: option to use current implicits and enchants --- src/Classes/ItemsTab.lua | 28 +++++++++++++++------ src/Classes/TradeQuery.lua | 38 ++++++++++++++++++++--------- src/Classes/TradeQueryGenerator.lua | 35 ++++++++++++-------------- 3 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index aaa060c8c7..4a1233e014 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -1585,14 +1585,14 @@ function ItemsTabClass:DeleteItem(item, deferUndoState) end end -local function copyAnointsAndEldritchImplicits(newItem, activeItemSet, items) - local newItemType = newItem.base.type - if activeItemSet[newItemType] then - local currentItem = activeItemSet[newItemType].selItemId and items[activeItemSet[newItemType].selItemId] +function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, migrateEldritchImplicits, overwrite) + local newItemType = newItem.base.weapon and "Weapon 1" or newItem.base.type + if self.activeItemSet[newItemType] then + local currentItem = self.activeItemSet[newItemType].selItemId and self.items[self.activeItemSet[newItemType].selItemId] -- if you don't have an equipped item that matches the type of the newItem, no need to do anything if currentItem then -- if the new item is anointable and does not have an anoint and your current respective item does, apply that anoint to the new item - if isAnointable(newItem) and #newItem.enchantModLines == 0 and activeItemSet[newItemType].selItemId > 0 then + if isAnointable(newItem) and (#newItem.enchantModLines == 0 or overwrite) and self.activeItemSet[newItemType].selItemId > 0 then local currentAnoint = currentItem.enchantModLines if currentAnoint and #currentAnoint == 1 then -- skip if amulet has more than one anoint e.g. Stranglegasp newItem.enchantModLines = currentAnoint @@ -1607,12 +1607,24 @@ local function copyAnointsAndEldritchImplicits(newItem, activeItemSet, items) return end end - if main.migrateEldritchImplicits and isValueInTable(eldritchBaseTypes, newItem.base.type) and isValueInTable(eldritchRarities, newItem.rarity) - and #newItem.implicitModLines == 0 and not newItem.corrupted and (currentItem.cleansing or currentItem.tangle) and currentItem.implicitModLines then + + local modifiableItem = not (newItem.corrupted or newItem.mirrored) + if migrateEldritchImplicits and isValueInTable(eldritchBaseTypes, newItem.base.type) and isValueInTable(eldritchRarities, newItem.rarity) + and (#newItem.implicitModLines == 0 or overwrite) and modifiableItem and (currentItem.cleansing or currentItem.tangle) and currentItem.implicitModLines then newItem.implicitModLines = currentItem.implicitModLines newItem.tangle = currentItem.tangle newItem.cleansing = currentItem.cleansing end + + -- harvest and heist enchantments on modifiable body armour or weapons + if newItem.base.weapon or newItem.base.type == "Body Armour" + and (#newItem.enchantModLines == 0 or overwrite) + and self.activeItemSet[newItemType].selItemId > 0 + and modifiableItem and currentItem.enchantModLines + then + newItem.enchantModLines = currentItem.enchantModLines + end + newItem:BuildAndParseRaw() end end @@ -1622,7 +1634,7 @@ end function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise) local newItem = new("Item", itemRaw) if newItem.base then - copyAnointsAndEldritchImplicits(newItem, self.activeItemSet, self.items) + self:CopyAnointsAndEldritchImplicits(newItem, main.migrateEldritchImplicits, false) if normalise then newItem:NormaliseQuality() newItem:BuildModList() diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 7ae9c339c7..d4397c579f 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -357,16 +357,23 @@ Highest Stat Value / Price - Sorts from highest to lowest Stat Value per currenc Lowest Price - Sorts from lowest to highest price of retrieved items Highest Weight - Displays the order retrieved from trade]] self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex) - self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, {-4, 0, 60, 16}, "^7Sort By:") - - -- Use Enchant in DPS sorting - self.controls.enchantInSort = new("CheckBoxControl", {"TOPRIGHT",self.controls.fetchCountEdit,"TOPLEFT"}, {-8, 0, row_height}, "Include Enchants:", function(state) - self.enchantInSort = state - for row_idx, _ in pairs(self.resultTbl) do - self:UpdateControlsWithItems(row_idx) - end - end) - self.controls.enchantInSort.tooltipText = "This includes enchants in sorting that occurs after trade results have been retrieved" + self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, {-4, 0, 56, 16}, "^7Sort By:") + + -- Implicit mod and enchant behaviour in searching and sorting + local eldritchTooltip = + [[Controls how eldritch implicits and enchants are treated. +Copy Current: current implicit modifiers and enchants are copied to the item before sorting, and eldritch weights are not used in the search. +Include Weights: eldritch mod weights are used for searching items and the results are not edited. +Ignored: eldritch mod weights are not used, enchants are removed before sorting. +Note that the search filter limit might make results with implicit weights misleading.]] + local eldritchOptions = { "Copy Current", "Include Weights", "Ignored" } + self.controls.eldritchEnchantMode = new("DropDownControl", { "TOPRIGHT", self.controls.fetchCountEdit, "TOPLEFT" }, + { -4, 0, 120, row_height }, + eldritchOptions, function(state) self.lastEldritchEnchantMode = state end, eldritchTooltip) + self.controls.eldritchEnchantMode:SetSel(self.lastEldritchEnchantMode or 1) + self.controls.eldritchEnchantLabel = new("LabelControl", { "RIGHT", self.controls.eldritchEnchantMode, "LEFT" }, + { -4, 0, 50, 16 }, + "^7Implicits and Enchants:") -- Realm selection self.controls.realmLabel = new("LabelControl", {"LEFT", self.controls.setSelect, "RIGHT"}, {18, 0, 20, row_height - 4}, "^7Realm:") @@ -762,10 +769,17 @@ function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, ba table.sort(result.evaluation, function(a, b) return a.weight > b.weight end) else local item = new("Item", result.item_string) - if not self.enchantInSort then -- Calc item DPS without anoint or enchant as these can generally be added after. - item.enchantModLines = { } + + -- if applicable: apply same eldritch implicits or anoints as equipped + if self.controls.eldritchEnchantMode:GetSelValue() == "Copy Current" then + self.itemsTab:CopyAnointsAndEldritchImplicits(item, true, true) + elseif self.controls.eldritchEnchantMode:GetSelValue() == "Ignored" then + item.enchantModLines = {} item:BuildAndParseRaw() end + -- edit the item string so that the user can see what was evaluated + result.item_string = item:BuildRaw() + local output = self:ReduceOutput(calcFunc({ repSlotName = slotName, repItem = item })) local weight = self.tradeQueryGenerator.WeightedRatioOutputs(baseOutput, output, self.statSortSelectionList) result.evaluation = {{ output = output, weight = weight }} diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 3959d63cfc..25faa6f4ca 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -924,13 +924,17 @@ function TradeQueryGeneratorClass:ExecuteQuery() if self.calcContext.options.includeScourge then self:GenerateModWeights(self.modData["Scourge"]) end - if self.calcContext.options.includeEldritch then + if self.calcContext.options.includeEldritch and + -- skip weights if we need an influenced item as they can produce really + -- bad results due to the filter limit + self.calcContext.options.influence1 == 1 and + self.calcContext.options.influence2 == 1 then self:GenerateModWeights(self.modData["Eater"]) self:GenerateModWeights(self.modData["Exarch"]) end - if self.calcContext.options.includeSynthesis then - self:GenerateModWeights(self.modData["Synthesis"]) - end + -- if self.calcContext.options.includeSynthesis then + -- self:GenerateModWeights(self.modData["Synthesis"]) + -- end end function TradeQueryGeneratorClass:addMoreWEMods() @@ -1184,7 +1188,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb local isAmuletSlot = slot and slot.slotName == "Amulet" local isEldritchModSlot = slot and eldritchModSlots[slot.slotName] == true - controls.includeCorrupted = new("CheckBoxControl", {"TOP",nil,"TOP"}, {-40, 30, 18}, "Corrupted Mods:", function(state) end) + controls.includeCorrupted = new("CheckBoxControl", {"TOP",nil,"TOP"}, {-40, 30, 18}, "Corrupted Mods:", function(state) end, "Includes corruption implicit modifiers in the weighted sum. Note that there is a maximum search filter count which means this might cause other weights to not be included.") controls.includeCorrupted.state = not context.slotTbl.alreadyCorrupted and (self.lastIncludeCorrupted == nil or self.lastIncludeCorrupted == true) controls.includeCorrupted.enabled = not context.slotTbl.alreadyCorrupted @@ -1214,7 +1218,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb if not isJewelSlot and not isAbyssalJewelSlot and includeScourge then controls.includeScourge = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Scourge Mods:", function(state) end) controls.includeScourge.state = (self.lastIncludeScourge == nil or self.lastIncludeScourge == true) - updateLastAnchor(controls.includeScourge) + updateLastAnchor(controls.includrecteScourge) end if isAmuletSlot then @@ -1223,12 +1227,6 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb updateLastAnchor(controls.includeTalisman) end - if isEldritchModSlot then - controls.includeEldritch = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Eldritch Mods:", function(state) end) - controls.includeEldritch.state = (self.lastIncludeEldritch == true) - updateLastAnchor(controls.includeEldritch) - end - if isJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 100, 18}, { "Any", "Base", "Abyss" }, function(index, value) end) controls.jewelType.selIndex = self.lastJewelType or 1 @@ -1315,19 +1313,18 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb main:ClosePopup() self.tradeTypeIndex = context.controls.tradeTypeSelection.selIndex - + options.includeEldritch = context.controls.eldritchEnchantMode:GetSelValue() == "Include Weights" and + isEldritchModSlot + options.useCurrentEnchantsImplicits = context.controls.eldritchEnchantMode:GetSelValue() == "Copy Current" if controls.includeMirrored then self.lastIncludeMirrored, options.includeMirrored = controls.includeMirrored.state, controls.includeMirrored.state end if controls.includeCorrupted then self.lastIncludeCorrupted, options.includeCorrupted = controls.includeCorrupted.state, controls.includeCorrupted.state end - if controls.includeSynthesis then - self.lastIncludeSynthesis, options.includeSynthesis = controls.includeSynthesis.state, controls.includeSynthesis.state - end - if controls.includeEldritch then - self.lastIncludeEldritch, options.includeEldritch = controls.includeEldritch.state, controls.includeEldritch.state - end + -- if controls.includeSynthesis then + -- self.lastIncludeSynthesis, options.includeSynthesis = controls.includeSynthesis.state, controls.includeSynthesis.state + -- end if controls.includeScourge then self.lastIncludeScourge, options.includeScourge = controls.includeScourge.state, controls.includeScourge.state end From e48cf3cd8e94667568b28b79ca0a2e9f596a4142 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:20:13 +0300 Subject: [PATCH 18/26] as per comments: move implicit and enchant overrides to query options --- src/Classes/ItemsTab.lua | 4 +- src/Classes/TradeQuery.lua | 46 ++++++++---------- src/Classes/TradeQueryGenerator.lua | 75 +++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 4a1233e014..a3641776d2 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -1585,7 +1585,7 @@ function ItemsTabClass:DeleteItem(item, deferUndoState) end end -function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, migrateEldritchImplicits, overwrite) +function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImplicits, overwrite) local newItemType = newItem.base.weapon and "Weapon 1" or newItem.base.type if self.activeItemSet[newItemType] then local currentItem = self.activeItemSet[newItemType].selItemId and self.items[self.activeItemSet[newItemType].selItemId] @@ -1609,7 +1609,7 @@ function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, migrateEldritchI end local modifiableItem = not (newItem.corrupted or newItem.mirrored) - if migrateEldritchImplicits and isValueInTable(eldritchBaseTypes, newItem.base.type) and isValueInTable(eldritchRarities, newItem.rarity) + if copyEldritchImplicits and isValueInTable(eldritchBaseTypes, newItem.base.type) and isValueInTable(eldritchRarities, newItem.rarity) and (#newItem.implicitModLines == 0 or overwrite) and modifiableItem and (currentItem.cleansing or currentItem.tangle) and currentItem.implicitModLines then newItem.implicitModLines = currentItem.implicitModLines newItem.tangle = currentItem.tangle diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index d4397c579f..b2ebd96be4 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -359,22 +359,6 @@ Highest Weight - Displays the order retrieved from trade]] self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex) self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, {-4, 0, 56, 16}, "^7Sort By:") - -- Implicit mod and enchant behaviour in searching and sorting - local eldritchTooltip = - [[Controls how eldritch implicits and enchants are treated. -Copy Current: current implicit modifiers and enchants are copied to the item before sorting, and eldritch weights are not used in the search. -Include Weights: eldritch mod weights are used for searching items and the results are not edited. -Ignored: eldritch mod weights are not used, enchants are removed before sorting. -Note that the search filter limit might make results with implicit weights misleading.]] - local eldritchOptions = { "Copy Current", "Include Weights", "Ignored" } - self.controls.eldritchEnchantMode = new("DropDownControl", { "TOPRIGHT", self.controls.fetchCountEdit, "TOPLEFT" }, - { -4, 0, 120, row_height }, - eldritchOptions, function(state) self.lastEldritchEnchantMode = state end, eldritchTooltip) - self.controls.eldritchEnchantMode:SetSel(self.lastEldritchEnchantMode or 1) - self.controls.eldritchEnchantLabel = new("LabelControl", { "RIGHT", self.controls.eldritchEnchantMode, "LEFT" }, - { -4, 0, 50, 16 }, - "^7Implicits and Enchants:") - -- Realm selection self.controls.realmLabel = new("LabelControl", {"LEFT", self.controls.setSelect, "RIGHT"}, {18, 0, 20, row_height - 4}, "^7Realm:") self.controls.realm = new("DropDownControl", {"LEFT", self.controls.realmLabel, "RIGHT"}, {6, 0, 150, row_height}, self.realmDropList, function(index, value) @@ -770,16 +754,6 @@ function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, ba else local item = new("Item", result.item_string) - -- if applicable: apply same eldritch implicits or anoints as equipped - if self.controls.eldritchEnchantMode:GetSelValue() == "Copy Current" then - self.itemsTab:CopyAnointsAndEldritchImplicits(item, true, true) - elseif self.controls.eldritchEnchantMode:GetSelValue() == "Ignored" then - item.enchantModLines = {} - item:BuildAndParseRaw() - end - -- edit the item string so that the user can see what was evaluated - result.item_string = item:BuildRaw() - local output = self:ReduceOutput(calcFunc({ repSlotName = slotName, repItem = item })) local weight = self.tradeQueryGenerator.WeightedRatioOutputs(baseOutput, output, self.statSortSelectionList) result.evaluation = {{ output = output, weight = weight }} @@ -965,6 +939,26 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro else self:SetNotice(context.controls.pbNotice, "") end + + -- replace eldritch mods or enchants if the user requested + -- so in TradeQueryGenerator + if self.tradeQueryGenerator.lastCopyEldritch or + self.tradeQueryGenerator.lastCopyEnchantMode == "Copy Current" then + ConPrintf("Replacing") + for i, _ in ipairs(items) do + local item = new("Item", items[i].item_string) + self.itemsTab:CopyAnointsAndEldritchImplicits(item, true, true) + items[i].item_string = item:BuildRaw() + end + elseif self.tradeQueryGenerator.lastCopyEnchantMode == "Remove" then + for i, _ in ipairs(items) do + local item = new("Item", items[i].item_string) + item.enchantModLines = {} + items[i].item_string = item:BuildRaw() + end + end + + self.resultTbl[context.row_idx] = items self:UpdateControlsWithItems(context.row_idx) context.controls["priceButton"..context.row_idx].label = "Price Item" diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 25faa6f4ca..74f5dbbc8f 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1186,9 +1186,11 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb local isJewelSlot = slot and slot.slotName:find("Jewel") ~= nil local isAbyssalJewelSlot = slot and slot.slotName:find("Abyssal") ~= nil local isAmuletSlot = slot and slot.slotName == "Amulet" + local isBeltSlot = slot and slot.slotName == "Belt" + local isWeaponSlot = slot and (slot.slotName == "Weapon 1" or slot.slotName == "Weapon 2") local isEldritchModSlot = slot and eldritchModSlots[slot.slotName] == true - controls.includeCorrupted = new("CheckBoxControl", {"TOP",nil,"TOP"}, {-40, 30, 18}, "Corrupted Mods:", function(state) end, "Includes corruption implicit modifiers in the weighted sum. Note that there is a maximum search filter count which means this might cause other weights to not be included.") + controls.includeCorrupted = new("CheckBoxControl", {"TOP",nil,"TOP"}, {-40, 30, 18}, "Corrupted Mods:", function(state) end, "Includes corruption implicit modifiers in the weighted sum.\nNote that there is a maximum search filter count which means this might cause other weights to not be included.") controls.includeCorrupted.state = not context.slotTbl.alreadyCorrupted and (self.lastIncludeCorrupted == nil or self.lastIncludeCorrupted == true) controls.includeCorrupted.enabled = not context.slotTbl.alreadyCorrupted @@ -1210,7 +1212,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb -- these unique items cannot be mirrored if not context.slotTbl.unique then - controls.includeMirrored = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Mirrored items:", function(state) end) + controls.includeMirrored = new("CheckBoxControl", {"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Mirrored Items:", function(state) end) controls.includeMirrored.state = (self.lastIncludeMirrored == nil or self.lastIncludeMirrored == true) updateLastAnchor(controls.includeMirrored) end @@ -1227,19 +1229,58 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb updateLastAnchor(controls.includeTalisman) end + -- Implicit mod and enchant behaviour in searching and sorting + if isEldritchModSlot then + controls.includeEldritch = new("CheckBoxControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 18 }, + "Eldritch Mods:", function(state) end, + "Includes corruption implicit modifiers in the weighted sum.\nNote that there is a maximum search filter count which means this might cause other weights to not be included.") + controls.includeEldritch.state = (self.lastIncludeEldritch == true) + updateLastAnchor(controls.includeEldritch) + + local eldritchTooltip = "Replaces the eldritch modifiers on search results with the eldritch modifiers from your currently equipped item." + local labelText = "Copy Current Implicits:" + controls.copyEldritch = new("CheckBoxControl", + { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, + { 0, 5, 18, 18 }, + labelText, function(state) end, eldritchTooltip, false) + controls.copyEldritch.state = self.lastCopyEldritch or false + updateLastAnchor(controls.copyEldritch) + end + if isAmuletSlot or isBeltSlot or isWeaponSlot then + local enchantTooltip = [[Keep: enchants will be unchanged on the search results. +Copy Current: current enchants will be applied to the search result items. +Remove: enchants will be removed from the search results.]] + local copyEnchantList = {"Keep", "Copy Current", "Remove"} + controls.copyEnchantMode = new("DropDownControl", + { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, + { 0, 5, 120, 18 }, + copyEnchantList, function(state) end, enchantTooltip) + controls.copyEnchantMode.state = self.lastCopyEnchantMode or false + controls.copyEnchantModeLabel = new("LabelControl", { "RIGHT", controls.copyEnchantMode, "LEFT" }, {-4, 0, 80, 16}, "^7Enchant Behaviour:") + updateLastAnchor(controls.copyEnchantMode) + end if isJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 100, 18}, { "Any", "Base", "Abyss" }, function(index, value) end) controls.jewelType.selIndex = self.lastJewelType or 1 controls.jewelTypeLabel = new("LabelControl", {"RIGHT",controls.jewelType,"LEFT"}, {-5, 0, 0, 16}, "Jewel Type:") updateLastAnchor(controls.jewelType) elseif slot and not isAbyssalJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then - controls.influence1 = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 100, 18}, influenceDropdownNames, function(index, value) end) - controls.influence1.selIndex = self.lastInfluence1 or 1 - controls.influence1Label = new("LabelControl", {"RIGHT",controls.influence1,"LEFT"}, {-5, 0, 0, 16}, "Influence 1:") - - controls.influence2 = new("DropDownControl", {"TOPLEFT",controls.influence1,"BOTTOMLEFT"}, {0, 5, 100, 18}, influenceDropdownNames, function(index, value) end) - controls.influence2.selIndex = self.lastInfluence2 or 1 - controls.influence2Label = new("LabelControl", {"RIGHT",controls.influence2,"LEFT"}, {-5, 0, 0, 16}, "Influence 2:") + local selFunc = function(_index, value) + -- influenced items can't have eldritch implicits + if controls.copyEldritchOrEnchant and isEldritchModSlot then + controls.copyEldritchOrEnchant.enabled = value == "None" + end + end + controls.influence1 = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, + influenceDropdownNames, selFunc) + controls.influence1:SetSel(self.lastInfluence1 or 1) + controls.influence1Label = new("LabelControl", {"RIGHT",controls.influence1,"LEFT"}, {-5, 0, 0, 16}, "^7Influence 1:") + + controls.influence2 = new("DropDownControl", { "TOPLEFT", controls.influence1, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, + influenceDropdownNames, selFunc) + controls.influence2:SetSel(self.lastInfluence2 or 1) + controls.influence2Label = new("LabelControl", { "RIGHT", controls.influence2, "LEFT" }, { -5, 0, 0, 16 }, + "^7Influence 2:") updateLastAnchor(controls.influence2, 46) elseif isAbyssalJewelSlot then controls.jewelType = new("DropDownControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 100, 18}, { "Abyss" }, nil) @@ -1247,7 +1288,6 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb controls.jewelTypeLabel = new("LabelControl", {"RIGHT",controls.jewelType,"LEFT"}, {-5, 0, 0, 16}, "Jewel Type:") updateLastAnchor(controls.jewelType) end - -- Add max price limit selection dropbox local currencyDropdownNames = { } for _, currency in ipairs(currencyTable) do @@ -1269,12 +1309,12 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb if slot and not isJewelSlot and not isAbyssalJewelSlot and not slot.slotName:find("Flask") then controls.sockets = new("EditControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 70, 18}, nil, nil, "%D") controls.sockets.buf = self.lastSockets and tostring(self.lastSockets) or "" - controls.socketsLabel = new("LabelControl", {"RIGHT",controls.sockets,"LEFT"}, {-5, 0, 0, 16}, "# of Empty Sockets:") + controls.socketsLabel = new("LabelControl", {"RIGHT",controls.sockets,"LEFT"}, {-5, 0, 0, 16}, "^7# of Empty Sockets:") updateLastAnchor(controls.sockets) if not slot.slotName:find("Belt") and not slot.slotName:find("Ring") and not slot.slotName:find("Amulet") then controls.links = new("EditControl", {"TOPLEFT",lastItemAnchor,"BOTTOMLEFT"}, {0, 5, 70, 18}, nil, nil, "%D") - controls.linksLabel = new("LabelControl", {"RIGHT",controls.links,"LEFT"}, {-5, 0, 0, 16}, "# of Links:") + controls.linksLabel = new("LabelControl", {"RIGHT",controls.links,"LEFT"}, {-5, 0, 0, 16}, "^7# of Links:") updateLastAnchor(controls.links) end end @@ -1313,9 +1353,10 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb main:ClosePopup() self.tradeTypeIndex = context.controls.tradeTypeSelection.selIndex - options.includeEldritch = context.controls.eldritchEnchantMode:GetSelValue() == "Include Weights" and - isEldritchModSlot - options.useCurrentEnchantsImplicits = context.controls.eldritchEnchantMode:GetSelValue() == "Copy Current" + + self.lastCopyEldritch = controls.copyEldritch and controls.copyEldritch.state + self.lastCopyEnchantMode = controls.copyEnchantMode and controls.copyEnchantMode:GetSelValue() + if controls.includeMirrored then self.lastIncludeMirrored, options.includeMirrored = controls.includeMirrored.state, controls.includeMirrored.state end @@ -1325,6 +1366,10 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb -- if controls.includeSynthesis then -- self.lastIncludeSynthesis, options.includeSynthesis = controls.includeSynthesis.state, controls.includeSynthesis.state -- end + if controls.includeEldritch then + self.lastIncludeEldritch, options.includeEldritch = controls.includeEldritch.state, + controls.includeEldritch.state + end if controls.includeScourge then self.lastIncludeScourge, options.includeScourge = controls.includeScourge.state, controls.includeScourge.state end From 0151db3c55969da1994eebf14bbf4ca6bf892c32 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:22:58 +0300 Subject: [PATCH 19/26] trader tool: add option to omit "while" eldritch mods and clarify anoint vs enchant for amulets and belts --- src/Classes/TradeQueryGenerator.lua | 59 +++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 74f5dbbc8f..51cd61d073 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -924,13 +924,32 @@ function TradeQueryGeneratorClass:ExecuteQuery() if self.calcContext.options.includeScourge then self:GenerateModWeights(self.modData["Scourge"]) end - if self.calcContext.options.includeEldritch and + if self.calcContext.options.includeEldritch ~= "None" and -- skip weights if we need an influenced item as they can produce really -- bad results due to the filter limit - self.calcContext.options.influence1 == 1 and + self.calcContext.options.influence1 == 1 and self.calcContext.options.influence2 == 1 then - self:GenerateModWeights(self.modData["Eater"]) - self:GenerateModWeights(self.modData["Exarch"]) + local omitConditional = self.calcContext.options.includeEldritch == "Omit While" + local eaterMods = self.modData["Eater"] + local exarchMods = self.modData["Exarch"] + if omitConditional then + local function filterMods(mods) + local filtered = {} + for name, mod in pairs(mods) do + -- the user might want to skip these because they're generally + -- not used much, but there are a lot of them and the higher + -- power causes them to take up a lot of filter slots + if not name:match(".*PinnaclePresence$") and not name:match(".*UniquePresence$") then + filtered[name] = mod + end + end + return filtered + end + eaterMods = filterMods(self.modData["Eater"]) + exarchMods = filterMods(self.modData["Exarch"]) + end + self:GenerateModWeights(eaterMods) + self:GenerateModWeights(exarchMods) end -- if self.calcContext.options.includeSynthesis then -- self:GenerateModWeights(self.modData["Synthesis"]) @@ -1231,10 +1250,17 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb -- Implicit mod and enchant behaviour in searching and sorting if isEldritchModSlot then - controls.includeEldritch = new("CheckBoxControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 18 }, - "Eldritch Mods:", function(state) end, - "Includes corruption implicit modifiers in the weighted sum.\nNote that there is a maximum search filter count which means this might cause other weights to not be included.") - controls.includeEldritch.state = (self.lastIncludeEldritch == true) + local eldritchTooltip = [[Controls the inclusion of eldritch mod weights in the weighted sum. +None: no weights are generated. +All: weights are generated for all eldritch implicit modifiers. +Omit while: weights are generated, but conditional "While unique/atlas boss" modifiers are skipped. +It is often not recommended to use "All" as this includes a lot of high power modifiers, +which will cause other useful modifiers to be left out in the weighted sum.]] + controls.includeEldritch = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 92, 18 }, + { "None", "All", "Omit While" }, function(_state) end, eldritchTooltip) + controls.includeEldritchLabel = new("LabelControl", { "RIGHT", controls.includeEldritch, "LEFT" }, + { -4, 0, 80, 16 }, "Eldritch Mods:") + controls.includeEldritch:SetSel(self.lastIncludeEldritch or 1) updateLastAnchor(controls.includeEldritch) local eldritchTooltip = "Replaces the eldritch modifiers on search results with the eldritch modifiers from your currently equipped item." @@ -1247,16 +1273,19 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb updateLastAnchor(controls.copyEldritch) end if isAmuletSlot or isBeltSlot or isWeaponSlot then - local enchantTooltip = [[Keep: enchants will be unchanged on the search results. -Copy Current: current enchants will be applied to the search result items. -Remove: enchants will be removed from the search results.]] - local copyEnchantList = {"Keep", "Copy Current", "Remove"} + local term = isWeaponSlot and "enchants" or "anoints" + local enchantTooltip = s_format([[Keep: %s will be unchanged on the search results. +Copy Current: current %s will be applied to the search result items. +Remove: %s will be removed from the search results.]], term, term, term) + local copyEnchantList = { "Keep", "Copy Current", "Remove" } controls.copyEnchantMode = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 120, 18 }, copyEnchantList, function(state) end, enchantTooltip) controls.copyEnchantMode.state = self.lastCopyEnchantMode or false - controls.copyEnchantModeLabel = new("LabelControl", { "RIGHT", controls.copyEnchantMode, "LEFT" }, {-4, 0, 80, 16}, "^7Enchant Behaviour:") + local labelText = isWeaponSlot and "^7Enchant Behaviour:" or "^7Anoint Behaviour:" + controls.copyEnchantModeLabel = new("LabelControl", { "RIGHT", controls.copyEnchantMode, "LEFT" }, + { -4, 0, 80, 16 }, labelText) updateLastAnchor(controls.copyEnchantMode) end if isJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then @@ -1367,8 +1396,8 @@ Remove: enchants will be removed from the search results.]] -- self.lastIncludeSynthesis, options.includeSynthesis = controls.includeSynthesis.state, controls.includeSynthesis.state -- end if controls.includeEldritch then - self.lastIncludeEldritch, options.includeEldritch = controls.includeEldritch.state, - controls.includeEldritch.state + self.lastIncludeEldritch, options.includeEldritch = controls.includeEldritch.selIndex, + controls.includeEldritch:GetSelValue() end if controls.includeScourge then self.lastIncludeScourge, options.includeScourge = controls.includeScourge.state, controls.includeScourge.state From ed2519d2ee88095d37b1b5e2a97aeaaa52ce8c0f Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:46:53 +0300 Subject: [PATCH 20/26] trader tool: fix #9678 crash --- src/Classes/TradeQuery.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index b2ebd96be4..cbc8d6255a 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -356,7 +356,10 @@ Highest Stat Value - Sort from highest to lowest Stat Value change of equipping Highest Stat Value / Price - Sorts from highest to lowest Stat Value per currency Lowest Price - Sorts from lowest to highest price of retrieved items Highest Weight - Displays the order retrieved from trade]] - self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex) + -- avoid calling selfunc to avoid updating controls before they are + -- initialised + -- https://github.com/PathOfBuildingCommunity/PathOfBuilding/issues/9678 + self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex, true) self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, {-4, 0, 56, 16}, "^7Sort By:") -- Realm selection From 2cbcfa23bd1ca1dab34f0f59162565803a6da968 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:19:37 +0300 Subject: [PATCH 21/26] trader tool: fix price based sorting #9678 --- src/Classes/TradeQuery.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index cbc8d6255a..cf3b931d4f 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -184,12 +184,17 @@ function TradeQueryClass:PriceBuilderProcessPoENinjaResponse(resp) if resp then -- Populate the chaos-converted values for each tradeId for currencyName, chaosEquivalent in pairs(resp) do + local currencyName = currencyName:lower() if self.currencyConversionTradeMap[currencyName] then self.pbCurrencyConversion[self.pbLeague][self.currencyConversionTradeMap[currencyName]] = chaosEquivalent else ConPrintf("Unhandled Currency Name: '"..currencyName.."'") end end + -- if nothing was actually found, we should add a notice + if #self.pbCurrencyConversion[self.pbLeague] == 0 then + self:SetNotice(self.controls.pbNotice, "PoE Ninja JSON Processing Error") + end else self:SetNotice(self.controls.pbNotice, "PoE Ninja JSON Processing Error") end From b29ac92b06433db3c3eaf3ae4a583b2dbf727d04 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:23:22 +0300 Subject: [PATCH 22/26] fix watcher's eye search crash on empty socket --- src/Classes/TradeQuery.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index cf3b931d4f..3630cce8d9 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -893,7 +893,8 @@ end -- Tries to first return an existing watcher's eye slot if possible function TradeQueryClass:findValidSlotForWatchersEye() for _, socket in pairs(self.itemsTab.sockets) do - if not socket.inactive and self.itemsTab.items[socket.selItemId].name:find("Watcher's Eye") then + local currentItem = not socket.inactive and self.itemsTab.items[socket.selItemId] + if currentItem and currentItem.name:find("Watcher's Eye") then return socket end end From 3ffe1995f2c24523865e83ab6b2a6c3d41714778 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:52:38 +0300 Subject: [PATCH 23/26] fix test for poe.ninja currencies --- spec/System/TestTradeQueryCurrency_spec.lua | 6 ++++-- src/Classes/TradeQuery.lua | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index 48b52f6f8a..45af5d1f55 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -40,16 +40,18 @@ describe("TradeQuery Currency Conversion", function() end) describe("PriceBuilderProcessPoENinjaResponse", function() - -- Pass: Processes without error, restoring map + -- Pass: Processes without error, restoring map while adding a notice -- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions it("handles unmapped currency", function() local orig_conv = mock_tradeQuery.currencyConversionTradeMap mock_tradeQuery.currencyConversionTradeMap = { div = "id" } + mock_tradeQuery.controls.pbNotice = { label = ""} local resp = { exotic = 10 } mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp) -- No crash expected assert.is_true(true) - mock_tradeQuery.currencyConversionTradeMap = orig_conv + assert.is_true(mock_tradeQuery.controls.pbNotice.label == "No currencies received from PoE Ninja") + mock_tradeQuery.currencyConversionTradeMap = orig_conv end) end) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 3630cce8d9..83900b61b7 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -193,7 +193,7 @@ function TradeQueryClass:PriceBuilderProcessPoENinjaResponse(resp) end -- if nothing was actually found, we should add a notice if #self.pbCurrencyConversion[self.pbLeague] == 0 then - self:SetNotice(self.controls.pbNotice, "PoE Ninja JSON Processing Error") + self:SetNotice(self.controls.pbNotice, "No currencies received from PoE Ninja") end else self:SetNotice(self.controls.pbNotice, "PoE Ninja JSON Processing Error") From c398ca911730fad54e3873af8e69c8c71d1a50a1 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Thu, 9 Apr 2026 03:17:07 +0300 Subject: [PATCH 24/26] trader tool: add price next to item name --- src/Classes/TradeQuery.lua | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 83900b61b7..2eb9af52a3 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -770,6 +770,22 @@ function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, ba end -- Method to update controls after a search is completed +function TradeQueryClass:UpdateDropdownList(row_idx) + local dropdownLabels = {} + + if not self.resultTbl[row_idx] then return end + + for result_index = 1, #self.resultTbl[row_idx] do + + local pb_index = self.sortedResultTbl[row_idx][result_index].index + local result = self.resultTbl[row_idx][pb_index] + local price = string.format(" %s(%d %s)", colorCodes["CURRENCY"], result.amount, result.currency) + local item = new("Item", result.item_string) + table.insert(dropdownLabels, colorCodes[item.rarity] .. item.name .. price) + end + self.controls["resultDropdown".. row_idx].selIndex = 1 + self.controls["resultDropdown".. row_idx]:SetList(dropdownLabels) +end function TradeQueryClass:UpdateControlsWithItems(row_idx) local sortMode = self.itemSortSelectionList[self.pbItemSortSelectionIndex] local sortedItems, errMsg = self:SortFetchResults(row_idx, sortMode) @@ -793,14 +809,7 @@ function TradeQueryClass:UpdateControlsWithItems(row_idx) amount = self.resultTbl[row_idx][pb_index].amount, } self.controls.fullPrice.label = "Total Price: " .. self:GetTotalPriceString() - local dropdownLabels = {} - for result_index = 1, #self.resultTbl[row_idx] do - local pb_index = self.sortedResultTbl[row_idx][result_index].index - local item = new("Item", self.resultTbl[row_idx][pb_index].item_string) - table.insert(dropdownLabels, colorCodes[item.rarity]..item.name) - end - self.controls["resultDropdown".. row_idx].selIndex = 1 - self.controls["resultDropdown".. row_idx]:SetList(dropdownLabels) + self:UpdateDropdownList(row_idx) end -- Method to set the current result return in the pane based of an index @@ -1051,15 +1060,11 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro self.controls.fullPrice.label = "Total Price: " .. self:GetTotalPriceString() end) controls["changeButton"..row_idx].shown = function() return self.resultTbl[row_idx] end - local dropdownLabels = {} - for _, sortedResult in ipairs(self.sortedResultTbl[row_idx] or {}) do - local item = new("Item", self.resultTbl[row_idx][sortedResult.index].item_string) - table.insert(dropdownLabels, colorCodes[item.rarity]..item.name) - end - controls["resultDropdown"..row_idx] = new("DropDownControl", { "TOPLEFT", controls["changeButton"..row_idx], "TOPRIGHT"}, {8, 0, 325, row_height}, dropdownLabels, function(index) + controls["resultDropdown"..row_idx] = new("DropDownControl", { "TOPLEFT", controls["changeButton"..row_idx], "TOPRIGHT"}, {8, 0, 325, row_height}, {}, function(index) self.itemIndexTbl[row_idx] = self.sortedResultTbl[row_idx][index].index self:SetFetchResultReturn(row_idx, self.itemIndexTbl[row_idx]) end) + self:UpdateDropdownList(row_idx) local function addMegalomaniacCompareToTooltipIfApplicable(tooltip, result_index) if slotTbl.slotName ~= "Megalomaniac" then return From bb83ede2fac74a17c2d35eb00a9d7931258a7abb Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Thu, 9 Apr 2026 03:38:41 +0300 Subject: [PATCH 25/26] fix empty check in PriceBuilderProcessPoENinjaResponse --- src/Classes/TradeQuery.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 2eb9af52a3..7742df1cc5 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -192,7 +192,7 @@ function TradeQueryClass:PriceBuilderProcessPoENinjaResponse(resp) end end -- if nothing was actually found, we should add a notice - if #self.pbCurrencyConversion[self.pbLeague] == 0 then + if next(self.pbCurrencyConversion[self.pbLeague]) == nil then self:SetNotice(self.controls.pbNotice, "No currencies received from PoE Ninja") end else From e82969fc0dd6cb91594bc18d9acc795da8259bf7 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:11:33 +0300 Subject: [PATCH 26/26] trader tool: use result - klog10(price) estimation for value sorting --- src/Classes/TradeQuery.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 7742df1cc5..81a4bbd895 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -358,7 +358,7 @@ on trade site to work on other leagues and realms)]] [[Weighted Sum searches will always sort using descending weighted sum Additional post filtering options can be done these include: Highest Stat Value - Sort from highest to lowest Stat Value change of equipping item -Highest Stat Value / Price - Sorts from highest to lowest Stat Value per currency +Highest Stat Value / Price - Sorts from highest to lowest by estimated Stat Value per currency Lowest Price - Sorts from lowest to highest price of retrieved items Highest Weight - Displays the order retrieved from trade]] -- avoid calling selfunc to avoid updating controls before they are @@ -858,6 +858,7 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) return newTbl elseif mode == self.sortModes.StatValue then for result_index = 1, #self.resultTbl[row_idx] do + ConPrintf("%.3f", getResultWeight(result_index)) t_insert(newTbl, { outputAttr = getResultWeight(result_index), index = result_index }) end table.sort(newTbl, function(a,b) return a.outputAttr > b.outputAttr end) @@ -867,7 +868,20 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) return nil, "MissingConversionRates" end for result_index = 1, #self.resultTbl[row_idx] do - t_insert(newTbl, { outputAttr = getResultWeight(result_index) / priceTable[result_index], index = result_index }) + -- generally, because we are filtering our results to only the top + -- contenders, we will end up with a small spread of result weights. + -- this is however not true for prices as *decent* items might start + -- at a couple of div while perfect items are worth hundreds of + -- divs. I think the best option here is weight - k * log10(price) + -- to prioritise good items while only slightly punishing high + -- prices. another option would be weight / log10(price), but it + -- still seems to overrate very cheap items that are bad + + -- scaling factor for price + local k = 0.03 + t_insert(newTbl, + { outputAttr = getResultWeight(result_index) - k * math.log(priceTable[result_index], 10), index = + result_index }) end table.sort(newTbl, function(a,b) return a.outputAttr > b.outputAttr end) elseif mode == self.sortModes.Price then