From bdb6ce4e0d5edc63e6c741d3574394cea4852c8b Mon Sep 17 00:00:00 2001 From: Borna Ivankovic Date: Mon, 25 Dec 2023 22:46:39 +0100 Subject: [PATCH 01/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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 7b18c37cfb71d617ed8586845adcb4109fb1cc01 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 21:30:07 +1000 Subject: [PATCH 11/44] Move mods to correct places Moves the mods to their correct areas in ModParser Removes parsing for mods that we do not currently support calcs for --- src/Data/ModCache.lua | 42 +++++++++++++-------------------------- src/Modules/ModParser.lua | 35 +++++++++++++------------------- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 2189799f13..bd26fd1e15 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -8263,8 +8263,7 @@ c["Debuffs on you expire 100% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfD c["Debuffs on you expire 15% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=15}},nil} c["Debuffs on you expire 18% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=18}},nil} c["Debuffs on you expire 20% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=20}},nil} -c["Debuffs on you expire 20% faster while affected by Haste"]={nil,"Debuffs on you expire 20% faster while affected by Haste "} -c["Debuffs on you expire 20% faster while affected by Haste You gain Onslaught for 4 seconds on Kill while affected by Haste"]={nil,"Debuffs on you expire 20% faster while affected by Haste You gain Onslaught for 4 seconds on Kill while affected by Haste "} +c["Debuffs on you expire 20% faster while affected by Haste"]={{[1]={[1]={type="Condition",var="AffectedByHaste"},flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=20}},nil} c["Debuffs on you expire 30% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=30}},nil} c["Debuffs on you expire 90% faster"]={{[1]={flags=0,keywordFlags=0,name="SelfDebuffExpirationRate",type="BASE",value=90}},nil} c["Defences are Zero"]={{[1]={flags=0,keywordFlags=0,name="Armour",type="MORE",value=-100},[2]={flags=0,keywordFlags=0,name="EnergyShield",type="MORE",value=-100},[3]={flags=0,keywordFlags=0,name="Evasion",type="MORE",value=-100},[4]={flags=0,keywordFlags=0,name="Ward",type="MORE",value=-100}},nil} @@ -9384,16 +9383,13 @@ c["Immune to Elemental Ailments while affected by Glorious Madness"]={{[1]={[1]= c["Immune to Elemental Ailments while on Consecrated Ground if you have at least 150 Devotion"]={{[1]={[1]={type="Condition",var="OnConsecratedGround"},[2]={stat="Devotion",threshold=150,type="StatThreshold"},flags=0,keywordFlags=0,name="ElementalAilmentImmune",type="FLAG",value=true}},nil} c["Immune to Exposure if you've cast Elemental Weakness in the past 10 seconds"]={{[1]={[1]={type="Condition",var="SelfCastElementalWeakness"},flags=0,keywordFlags=0,name="ExposureImmune",type="FLAG",value=true}},nil} c["Immune to Freeze and Chill while Ignited"]={{[1]={[1]={type="Condition",var="Ignited"},flags=0,keywordFlags=0,name="FreezeImmune",type="FLAG",value=true},[2]={[1]={type="Condition",var="Ignited"},flags=0,keywordFlags=0,name="ChillImmune",type="FLAG",value=true}},nil} -c["Immune to Freeze while affected by Purity of Ice"]={nil,"Immune to Freeze while affected by Purity of Ice "} -c["Immune to Freeze while affected by Purity of Ice 10% of Physical Damage from Hits taken as Cold Damage while affected by Purity of Ice"]={nil,"Immune to Freeze while affected by Purity of Ice 10% of Physical Damage from Hits taken as Cold Damage while affected by Purity of Ice "} +c["Immune to Freeze while affected by Purity of Ice"]={{[1]={[1]={type="Condition",var="AffectedByPurityofIce"},flags=0,keywordFlags=0,name="FreezeImmune",type="FLAG",value=true}},nil} c["Immune to Ignite and Shock"]={{[1]={flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil} -c["Immune to Ignite while affected by Purity of Fire"]={nil,"Immune to Ignite while affected by Purity of Fire "} -c["Immune to Ignite while affected by Purity of Fire 10% of Physical Damage from Hits taken as Fire Damage while affected by Purity of Fire"]={nil,"Immune to Ignite while affected by Purity of Fire 10% of Physical Damage from Hits taken as Fire Damage while affected by Purity of Fire "} +c["Immune to Ignite while affected by Purity of Fire"]={{[1]={[1]={type="Condition",var="AffectedByPurityofFire"},flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil} c["Immune to Poison if Equipped Helmet has higher Evasion Rating than Armour"]={{[1]={[1]={type="Condition",var="HelmetEvasionHigherThanArmour"},flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil} c["Immune to Reflected Damage if you've cast Punishment in the past 10 seconds"]={nil,"Immune to Reflected Damage if you've cast Punishment in the past 10 seconds "} c["Immune to Reflected Damage if you've cast Punishment in the past 10 seconds Intimidate Enemies on Hit if you've cast Punishment in the past 10 seconds"]={nil,"Immune to Reflected Damage if you've cast Punishment in the past 10 seconds Intimidate Enemies on Hit if you've cast Punishment in the past 10 seconds "} -c["Immune to Shock while affected by Purity of Lightning"]={nil,"Immune to Shock while affected by Purity of Lightning "} -c["Immune to Shock while affected by Purity of Lightning 10% of Physical Damage from Hits taken as Lightning Damage while affected by Purity of Lightning"]={nil,"Immune to Shock while affected by Purity of Lightning 10% of Physical Damage from Hits taken as Lightning Damage while affected by Purity of Lightning "} +c["Immune to Shock while affected by Purity of Lightning"]={{[1]={[1]={type="Condition",var="AffectedByPurityofLightning"},flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil} c["Immunity to Freeze, Chill, Curses and Stuns during Effect"]={{[1]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="FreezeImmune",type="FLAG",value=true},[2]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="ChillImmune",type="FLAG",value=true},[3]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="CurseImmune",type="FLAG",value=true},[4]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil} c["Impale Damage dealt to Enemies Impaled by you Overwhelms 10% Physical Damage Reduction"]={{[1]={flags=0,keywordFlags=0,name="EnemyImpalePhysicalDamageReduction",type="BASE",value=-10}},nil} c["Impale Damage dealt to Enemies Impaled by you ignores Enemy Physical Damage Reduction"]={{[1]={flags=0,keywordFlags=0,name="IgnoreEnemyImpalePhysicalDamageReduction",type="FLAG",value=true}},nil} @@ -12245,8 +12241,7 @@ c["Triggers Level 20 Summon Arbalists when Equipped"]={{[1]={flags=0,keywordFlag c["Triggers Level 20 Summon Triggerbots when Allocated"]={{[1]={flags=0,keywordFlags=0,name="HaveTriggerBots",type="FLAG",value=true}},nil} c["Triggers Level 7 Abberath's Fury when Equipped"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=7,skillId="RepeatingShockwave",triggered=true}}},nil} c["Unaffected by Bleeding"]={{[1]={flags=0,keywordFlags=0,name="SelfBleedEffect",type="MORE",value=-100}},nil} -c["Unaffected by Bleeding while affected by Malevolence"]={nil,"Unaffected by Bleeding while affected by Malevolence "} -c["Unaffected by Bleeding while affected by Malevolence Unaffected by Poison while affected by Malevolence"]={nil,"Unaffected by Bleeding while affected by Malevolence Unaffected by Poison while affected by Malevolence "} +c["Unaffected by Bleeding while affected by Malevolence"]={{[1]={[1]={type="Condition",var="AffectedByMalevolence"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="SelfBleedEffect",type="MORE",value=-100}},nil} c["Unaffected by Burning Ground"]={nil,"Unaffected by Burning Ground "} c["Unaffected by Burning Ground while affected by Purity of Fire"]={nil,"Unaffected by Burning Ground while affected by Purity of Fire "} c["Unaffected by Burning Ground while affected by Purity of Fire Unaffected by Flammability while affected by Purity of Fire"]={nil,"Unaffected by Burning Ground while affected by Purity of Fire Unaffected by Flammability while affected by Purity of Fire "} @@ -12256,27 +12251,21 @@ c["Unaffected by Chill while Leeching Mana"]={{[1]={[1]={type="Condition",var="L c["Unaffected by Chilled Ground"]={nil,"Unaffected by Chilled Ground "} c["Unaffected by Chilled Ground while affected by Purity of Ice"]={nil,"Unaffected by Chilled Ground while affected by Purity of Ice "} c["Unaffected by Chilled Ground while affected by Purity of Ice Unaffected by Frostbite while affected by Purity of Ice"]={nil,"Unaffected by Chilled Ground while affected by Purity of Ice Unaffected by Frostbite while affected by Purity of Ice "} -c["Unaffected by Conductivity while affected by Purity of Lightning"]={nil,"Unaffected by Conductivity while affected by Purity of Lightning "} -c["Unaffected by Conductivity while affected by Purity of Lightning Unaffected by Shocked Ground while affected by Purity of Lightning"]={nil,"Unaffected by Conductivity while affected by Purity of Lightning Unaffected by Shocked Ground while affected by Purity of Lightning "} +c["Unaffected by Conductivity while affected by Purity of Lightning"]={{[1]={[1]={type="SkillName",var="Conductivity"},[2]={type="Condition",var="AffectedByPurityofLightning"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Curses"]={{[1]={[1]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Curses while affected by Zealotry"]={{[1]={[1]={type="Condition",var="AffectedByZealotry"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Damaging Ailments"]={{[1]={flags=0,keywordFlags=0,name="SelfBleedEffect",type="MORE",value=-100},[2]={flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100},[3]={flags=0,keywordFlags=0,name="SelfPoisonEffect",type="MORE",value=-100}},nil} c["Unaffected by Desecrated Ground"]={nil,"Unaffected by Desecrated Ground "} -c["Unaffected by Elemental Weakness while affected by Purity of Elements"]={nil,"Unaffected by Elemental Weakness while affected by Purity of Elements "} -c["Unaffected by Elemental Weakness while affected by Purity of Elements 20% of Cold and Lightning Damage taken as Fire Damage while affected by Purity of Fire"]={nil,"Unaffected by Elemental Weakness while affected by Purity of Elements 20% of Cold and Lightning Damage taken as Fire Damage while affected by Purity of Fire "} -c["Unaffected by Enfeeble while affected by Grace"]={nil,"Unaffected by Enfeeble while affected by Grace "} -c["Unaffected by Enfeeble while affected by Grace +8% chance to Suppress Spell Damage while affected by Haste"]={nil,"Unaffected by Enfeeble while affected by Grace +8% chance to Suppress Spell Damage while affected by Haste "} -c["Unaffected by Flammability while affected by Purity of Fire"]={nil,"Unaffected by Flammability while affected by Purity of Fire "} -c["Unaffected by Flammability while affected by Purity of Fire 20% of Fire and Lightning Damage taken as Cold Damage while affected by Purity of Ice"]={nil,"Unaffected by Flammability while affected by Purity of Fire 20% of Fire and Lightning Damage taken as Cold Damage while affected by Purity of Ice "} -c["Unaffected by Frostbite while affected by Purity of Ice"]={nil,"Unaffected by Frostbite while affected by Purity of Ice "} -c["Unaffected by Frostbite while affected by Purity of Ice 20% of Fire and Cold Damage taken as Lightning Damage while"]={nil,"Unaffected by Frostbite while affected by Purity of Ice 20% of Fire and Cold Damage taken as Lightning Damage while "} +c["Unaffected by Elemental Weakness while affected by Purity of Elements"]={{[1]={[1]={type="SkillName",var="Elemental Weakness"},[2]={type="Condition",var="AffectedByPurityofElements"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Enfeeble while affected by Grace"]={{[1]={[1]={type="SkillName",var="Enfeeble"},[2]={type="Condition",var="AffectedByGrace"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Flammability while affected by Purity of Fire"]={{[1]={[1]={type="SkillName",var="Flammability"},[2]={type="Condition",var="AffectedByPurityofFire"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Frostbite while affected by Purity of Ice"]={{[1]={[1]={type="SkillName",var="Frostbite"},[2]={type="Condition",var="AffectedByPurityofIce"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Ignite"]={{[1]={flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100}},nil} c["Unaffected by Ignite if 2 Warlord Items are Equipped"]={{[1]={[1]={threshold=2,type="MultiplierThreshold",var="WarlordItem"},flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100}},nil} c["Unaffected by Ignite or Shock if Maximum Life and Maximum Mana are within 500"]={nil,"Unaffected by Ignite or Shock if Maximum Life and Maximum Mana are within 500 "} c["Unaffected by Poison"]={{[1]={flags=0,keywordFlags=0,name="SelfPoisonEffect",type="MORE",value=-100}},nil} c["Unaffected by Poison if 2 Hunter Items are Equipped"]={{[1]={[1]={threshold=2,type="MultiplierThreshold",var="HunterItem"},flags=0,keywordFlags=0,name="SelfPoisonEffect",type="MORE",value=-100}},nil} -c["Unaffected by Poison while affected by Malevolence"]={nil,"Unaffected by Poison while affected by Malevolence "} -c["Unaffected by Poison while affected by Malevolence Damaging Ailments you inflict deal Damage 15% faster while affected by Malevolence"]={nil,"Unaffected by Poison while affected by Malevolence Damaging Ailments you inflict deal Damage 15% faster while affected by Malevolence "} +c["Unaffected by Poison while affected by Malevolence"]={{[1]={[1]={type="Condition",var="AffectedByMalevolence"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="SelfPoisonEffect",type="MORE",value=-100}},nil} c["Unaffected by Shock"]={{[1]={flags=0,keywordFlags=0,name="SelfShockEffect",type="MORE",value=-100}},nil} c["Unaffected by Shock if 2 Crusader Items are Equipped"]={{[1]={[1]={threshold=2,type="MultiplierThreshold",var="CrusaderItem"},flags=0,keywordFlags=0,name="SelfShockEffect",type="MORE",value=-100}},nil} c["Unaffected by Shock while Leeching Energy Shield"]={{[1]={[1]={type="Condition",var="LeechingEnergyShield"},flags=0,keywordFlags=0,name="SelfShockEffect",type="MORE",value=-100}},nil} @@ -12285,10 +12274,8 @@ c["Unaffected by Shocked Ground while affected by Purity of Lightning"]={nil,"Un c["Unaffected by Shocked Ground while affected by Purity of Lightning 1.5% of Damage leeched as Life while affected by Vitality"]={nil,"Unaffected by Shocked Ground while affected by Purity of Lightning 1.5% of Damage leeched as Life while affected by Vitality "} c["Unaffected by Shocked Ground while affected by Purity of Lightning 2% of Damage leeched as Life while affected by Vitality"]={nil,"Unaffected by Shocked Ground while affected by Purity of Lightning 2% of Damage leeched as Life while affected by Vitality "} c["Unaffected by Temporal Chains"]={{[1]={[1]={skillName="Temporal Chains",type="SkillName"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} -c["Unaffected by Temporal Chains while affected by Haste"]={nil,"Unaffected by Temporal Chains while affected by Haste "} -c["Unaffected by Temporal Chains while affected by Haste Adds 70 to 104 Cold Damage while affected by Hatred"]={{[1]={[1]={includeTransfigured=true,skillName="Temporal Chains",type="SkillName"},flags=0,keywordFlags=0,name="ColdMin",type="BASE",value=70},[2]={[1]={includeTransfigured=true,skillName="Temporal Chains",type="SkillName"},flags=0,keywordFlags=0,name="ColdMax",type="BASE",value=104}},"Unaffected bywhile affected by Haste while affected by Hatred "} -c["Unaffected by Vulnerability while affected by Determination"]={nil,"Unaffected by Vulnerability while affected by Determination "} -c["Unaffected by Vulnerability while affected by Determination +8% Chance to Block Spell Damage while affected by Discipline"]={nil,"Unaffected by Vulnerability while affected by Determination +8% Chance to Block Spell Damage while affected by Discipline "} +c["Unaffected by Temporal Chains while affected by Haste"]={{[1]={[1]={skillName="Temporal Chains",type="SkillName"},[2]={type="Condition",var="AffectedByHaste"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Vulnerability while affected by Determination"]={{[1]={[1]={type="SkillName",var="Vulnerability"},[2]={type="Condition",var="AffectedByDetermination"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unattached Brands gain 20% increased Brand Attachment Range per"]={nil,"Unattached Brands gain 20% increased Brand Attachment Range per "} c["Unattached Brands gain 20% increased Brand Attachment Range per second, up to a maximum of 100%"]={nil,"Unattached Brands gain 20% increased Brand Attachment Range per second, up to a maximum of 100% "} c["Unholy Might"]={{[1]={flags=0,keywordFlags=0,name="Condition:UnholyMight",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="Condition:CanWither",type="FLAG",value=true}},nil} @@ -12768,8 +12755,7 @@ c["Your Fire Damage can Poison"]={{[1]={flags=0,keywordFlags=0,name="FireCanPois c["Your Fire Damage can Shock but not Ignite"]={{[1]={flags=0,keywordFlags=0,name="FireCanShock",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="FireCannotIgnite",type="FLAG",value=true}},nil} c["Your Hexes can affect Hexproof Enemies"]={{[1]={flags=0,keywordFlags=0,name="CursesIgnoreHexproof",type="FLAG",value=true}},nil} c["Your Hexes have infinite Duration"]={{[1]={[1]={skillType=79,type="SkillType"},flags=0,keywordFlags=0,name="Duration",type="BASE",value=math.huge}},nil} -c["Your Hits Intimidate Enemies for 4 seconds while you are using Pride"]={nil,"Your Hits Intimidate Enemies for 4 seconds while you are using Pride "} -c["Your Hits Intimidate Enemies for 4 seconds while you are using Pride +50% to Chaos Resistance while affected by Purity of Elements"]={nil,"Your Hits Intimidate Enemies for 4 seconds while you are using Pride +50% to Chaos Resistance while affected by Purity of Elements "} +c["Your Hits Intimidate Enemies for 4 seconds while you are using Pride"]={{[1]={[1]={type="Condition",var="AffectedByPride"},flags=0,keywordFlags=0,name="EnemyModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Condition:Intimidated",type="FLAG",value=true}}}},nil} c["Your Hits always inflict Freeze, Shock and Ignite while Unbound"]={{[1]={[1]={type="Condition",var="Unbound"},flags=0,keywordFlags=0,name="EnemyFreezeChance",type="BASE",value=100},[2]={[1]={type="Condition",var="Unbound"},flags=0,keywordFlags=0,name="EnemyShockChance",type="BASE",value=100},[3]={[1]={type="Condition",var="Unbound"},flags=0,keywordFlags=0,name="EnemyIgniteChance",type="BASE",value=100}},nil} c["Your Hits are always Critical Strikes"]={{[1]={flags=0,keywordFlags=0,name="CritChance",type="OVERRIDE",value=100}},nil} c["Your Hits can only Kill Frozen Enemies"]={nil,"Your Hits can only Kill Frozen Enemies "} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 7ea697696e..21bf84b0d3 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4131,6 +4131,7 @@ local specialModList = { ["([%a%s]+) has (%d+)%% increased effect"] = function(_, skill, num) return { mod("BuffEffect", "INC", num, { type = "SkillId", skillId = gemIdLookup[skill]}) } end, ["debuffs on you expire (%d+)%% faster"] = function(num) return { mod("SelfDebuffExpirationRate", "BASE", num) } end, ["debuffs on you expire (%d+)%% slower"] = function(num) return { mod("SelfDebuffExpirationRate", "BASE", -num) } end, + ["debuffs on you expire (%d+)%% faster while affected by haste"] = function(num) return { mod("SelfDebuffExpirationRate", "BASE", num, { type = "Condition", var = "AffectedByHaste"}) } end, ["warcries debilitate enemies for (%d+) seconds?"] = { mod("DebilitateChance", "BASE", 100) }, ["debilitate enemies for (%d+) seconds? when you suppress their spell damage"] = { mod("DebilitateChance", "BASE", 100) }, ["debilitate nearby enemies for (%d+) seconds? when f?l?a?s?k? ?effect ends"] = { mod("DebilitateChance", "BASE", 100) }, @@ -4680,6 +4681,9 @@ local specialModList = { ["immun[ei]t?y? to chill"] = { flag("ChillImmune"), }, ["cannot be ignited"] = { flag("IgniteImmune"), }, ["immun[ei]t?y? to ignite"] = { flag("IgniteImmune"), }, + ["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" }) }, ["critical strikes against you do not inherently inflict elemental ailments"] = { flag("CritsOnYouDontAlwaysApplyElementalAilments"), }, ["cannot be ignited while at maximum endurance charges"] = { flag("IgniteImmune", {type = "StatThreshold", stat = "EnduranceCharges", thresholdStat = "EnduranceChargesMax" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }), }, ["grants immunity to ignite for (%d+) seconds if used while ignited"] = { flag("IgniteImmune", { type = "Condition", var = "UsingFlask" }), }, @@ -4756,6 +4760,13 @@ local specialModList = { ["unaffected by curses"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["you are immune to curses"] = { flag("CurseImmune") }, ["unaffected by curses while affected by zealotry"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "Condition", var = "AffectedByZealotry" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by vulnerability while affected by determination"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Vulnerability" }, { type = "Condition", var = "AffectedByDetermination" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by enfeeble while affected by grace"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Enfeeble" }, { type = "Condition", var = "AffectedByGrace" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by temporal chains while affected by haste"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Temporal Chains" }, { type = "Condition", var = "AffectedByHaste" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by elemental weakness while affected by purity of elements"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Elemental Weakness" }, { type = "Condition", var = "AffectedByPurityofElements" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by flammability while affected by purity of fire"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Flammability" }, { type = "Condition", var = "AffectedByPurityofFire" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by frostbite while affected by purity of ice"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Frostbite" }, { type = "Condition", var = "AffectedByPurityofIce" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by conductivity while affected by purity of lightning"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Conductivity" }, { type = "Condition", var = "AffectedByPurityofLightning" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["immun[ei]t?y? to curses while you have at least (%d+) rage"] = function(num) return { flag("CurseImmune", { type = "MultiplierThreshold", var = "Rage", threshold = num }) } end, ["you cannot be cursed with silence"] = { flag("SilenceImmune") }, ["unaffected by ignite"] = { mod("SelfIgniteEffect", "MORE", -100) }, @@ -4774,6 +4785,8 @@ local specialModList = { mod("SelfIgniteEffect", "MORE", -100), mod("SelfPoisonEffect", "MORE", -100), }, + ["unaffected by bleeding while affected by malevolence"] = { mod("SelfBleedEffect", "MORE", -100, { type = "Condition", var = "AffectedByMalevolence" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by poison while affected by malevolence"] = { mod("SelfPoisonEffect", "MORE", -100, { type = "Condition", var = "AffectedByMalevolence" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["unaffected by (.+) if (%d+) (%a+) items are equipped"] = function (_, ailment, thresh, influence) return { mod("Self"..ailment:gsub("^%l", string.upper).."Effect", "MORE", -100, { type = "MultiplierThreshold", var = firstToUpper(influence) .. "Item", threshold = tonumber(thresh) }) } end, @@ -4867,6 +4880,7 @@ local specialModList = { -- MultiplierThreshold is on RageStacks because Rage is only set in CalcPerform if Condition:CanGainRage is true, Bear's Girdle does not flag CanGainRage mod("EnemyModifier", "LIST", { mod = flag("Condition:Intimidated") }, { type = "MultiplierThreshold", var = "RageStack", threshold = 1 }) }, + ["your hits intimidate enemies for (%d+) seconds while you are using pride"] = { mod("EnemyModifier", "LIST", { mod = flag("Condition:Intimidated") }, { type = "Condition", var = "AffectedByPride" }) }, -- Flasks ["flasks do not apply to you"] = { flag("FlasksDoNotApplyToPlayer") }, ["flasks apply to your zombies and spectres"] = { flag("FlasksApplyToMinion", { type = "SkillName", skillNameList = { "Raise Zombie", "Raise Spectre" }, includeTransfigured = true }) }, @@ -5680,27 +5694,6 @@ 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" }) }, - ["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"}) }, ["physical skills have (%d+)%% increased duration per (%d+) intelligence"] = function(num1, _, num2) return { mod("Duration", "INC", num1, nil, nil, KeywordFlag.Physical, { type = "PerStat", stat = "Int", div = tonumber(num2) }) } end, From 3f8a5403e5bd51ae599c49050bb0ff9f916887df Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 21:35:49 +1000 Subject: [PATCH 12/44] Fix crash when opening trader with empty jewel 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 157a4f92b9..f102f193c3 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -882,7 +882,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 socketItem = self.itemsTab.items[socket.selItemId] + if not socket.inactive and socketItem and socketItem.name and socketItem.name:find("Watcher's Eye") then return socket end end From 780e1868ed0e20c37644cf671cdae1728be5e864 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 21:49:47 +1000 Subject: [PATCH 13/44] Fix skill name parsing --- src/Data/ModCache.lua | 12 ++++++------ src/Modules/ModParser.lua | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index bd26fd1e15..981b6801b5 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -12251,15 +12251,15 @@ c["Unaffected by Chill while Leeching Mana"]={{[1]={[1]={type="Condition",var="L c["Unaffected by Chilled Ground"]={nil,"Unaffected by Chilled Ground "} c["Unaffected by Chilled Ground while affected by Purity of Ice"]={nil,"Unaffected by Chilled Ground while affected by Purity of Ice "} c["Unaffected by Chilled Ground while affected by Purity of Ice Unaffected by Frostbite while affected by Purity of Ice"]={nil,"Unaffected by Chilled Ground while affected by Purity of Ice Unaffected by Frostbite while affected by Purity of Ice "} -c["Unaffected by Conductivity while affected by Purity of Lightning"]={{[1]={[1]={type="SkillName",var="Conductivity"},[2]={type="Condition",var="AffectedByPurityofLightning"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Conductivity while affected by Purity of Lightning"]={{[1]={[1]={skillName="Conductivity",type="SkillName"},[2]={type="Condition",var="AffectedByPurityofLightning"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Curses"]={{[1]={[1]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Curses while affected by Zealotry"]={{[1]={[1]={type="Condition",var="AffectedByZealotry"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Damaging Ailments"]={{[1]={flags=0,keywordFlags=0,name="SelfBleedEffect",type="MORE",value=-100},[2]={flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100},[3]={flags=0,keywordFlags=0,name="SelfPoisonEffect",type="MORE",value=-100}},nil} c["Unaffected by Desecrated Ground"]={nil,"Unaffected by Desecrated Ground "} -c["Unaffected by Elemental Weakness while affected by Purity of Elements"]={{[1]={[1]={type="SkillName",var="Elemental Weakness"},[2]={type="Condition",var="AffectedByPurityofElements"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} -c["Unaffected by Enfeeble while affected by Grace"]={{[1]={[1]={type="SkillName",var="Enfeeble"},[2]={type="Condition",var="AffectedByGrace"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} -c["Unaffected by Flammability while affected by Purity of Fire"]={{[1]={[1]={type="SkillName",var="Flammability"},[2]={type="Condition",var="AffectedByPurityofFire"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} -c["Unaffected by Frostbite while affected by Purity of Ice"]={{[1]={[1]={type="SkillName",var="Frostbite"},[2]={type="Condition",var="AffectedByPurityofIce"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Elemental Weakness while affected by Purity of Elements"]={{[1]={[1]={skillName="Elemental Weakness",type="SkillName"},[2]={type="Condition",var="AffectedByPurityofElements"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Enfeeble while affected by Grace"]={{[1]={[1]={skillName="Enfeeble",type="SkillName"},[2]={type="Condition",var="AffectedByGrace"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Flammability while affected by Purity of Fire"]={{[1]={[1]={skillName="Flammability",type="SkillName"},[2]={type="Condition",var="AffectedByPurityofFire"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Frostbite while affected by Purity of Ice"]={{[1]={[1]={skillName="Frostbite",type="SkillName"},[2]={type="Condition",var="AffectedByPurityofIce"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Ignite"]={{[1]={flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100}},nil} c["Unaffected by Ignite if 2 Warlord Items are Equipped"]={{[1]={[1]={threshold=2,type="MultiplierThreshold",var="WarlordItem"},flags=0,keywordFlags=0,name="SelfIgniteEffect",type="MORE",value=-100}},nil} c["Unaffected by Ignite or Shock if Maximum Life and Maximum Mana are within 500"]={nil,"Unaffected by Ignite or Shock if Maximum Life and Maximum Mana are within 500 "} @@ -12275,7 +12275,7 @@ c["Unaffected by Shocked Ground while affected by Purity of Lightning 1.5% of Da c["Unaffected by Shocked Ground while affected by Purity of Lightning 2% of Damage leeched as Life while affected by Vitality"]={nil,"Unaffected by Shocked Ground while affected by Purity of Lightning 2% of Damage leeched as Life while affected by Vitality "} c["Unaffected by Temporal Chains"]={{[1]={[1]={skillName="Temporal Chains",type="SkillName"},[2]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unaffected by Temporal Chains while affected by Haste"]={{[1]={[1]={skillName="Temporal Chains",type="SkillName"},[2]={type="Condition",var="AffectedByHaste"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} -c["Unaffected by Vulnerability while affected by Determination"]={{[1]={[1]={type="SkillName",var="Vulnerability"},[2]={type="Condition",var="AffectedByDetermination"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} +c["Unaffected by Vulnerability while affected by Determination"]={{[1]={[1]={skillName="Vulnerability",type="SkillName"},[2]={type="Condition",var="AffectedByDetermination"},[3]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="CurseEffectOnSelf",type="MORE",value=-100}},nil} c["Unattached Brands gain 20% increased Brand Attachment Range per"]={nil,"Unattached Brands gain 20% increased Brand Attachment Range per "} c["Unattached Brands gain 20% increased Brand Attachment Range per second, up to a maximum of 100%"]={nil,"Unattached Brands gain 20% increased Brand Attachment Range per second, up to a maximum of 100% "} c["Unholy Might"]={{[1]={flags=0,keywordFlags=0,name="Condition:UnholyMight",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="Condition:CanWither",type="FLAG",value=true}},nil} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 21bf84b0d3..89f358cc5a 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4760,13 +4760,13 @@ local specialModList = { ["unaffected by curses"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["you are immune to curses"] = { flag("CurseImmune") }, ["unaffected by curses while affected by zealotry"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "Condition", var = "AffectedByZealotry" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by vulnerability while affected by determination"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Vulnerability" }, { type = "Condition", var = "AffectedByDetermination" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by enfeeble while affected by grace"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Enfeeble" }, { type = "Condition", var = "AffectedByGrace" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by vulnerability while affected by determination"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Vulnerability" }, { type = "Condition", var = "AffectedByDetermination" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by enfeeble while affected by grace"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Enfeeble" }, { type = "Condition", var = "AffectedByGrace" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["unaffected by temporal chains while affected by haste"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Temporal Chains" }, { type = "Condition", var = "AffectedByHaste" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by elemental weakness while affected by purity of elements"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Elemental Weakness" }, { type = "Condition", var = "AffectedByPurityofElements" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by flammability while affected by purity of fire"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Flammability" }, { type = "Condition", var = "AffectedByPurityofFire" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by frostbite while affected by purity of ice"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Frostbite" }, { type = "Condition", var = "AffectedByPurityofIce" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, - ["unaffected by conductivity while affected by purity of lightning"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", var = "Conductivity" }, { type = "Condition", var = "AffectedByPurityofLightning" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by elemental weakness while affected by purity of elements"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Elemental Weakness" }, { type = "Condition", var = "AffectedByPurityofElements" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by flammability while affected by purity of fire"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Flammability" }, { type = "Condition", var = "AffectedByPurityofFire" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by frostbite while affected by purity of ice"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Frostbite" }, { type = "Condition", var = "AffectedByPurityofIce" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, + ["unaffected by conductivity while affected by purity of lightning"] = { mod("CurseEffectOnSelf", "MORE", -100, { type = "SkillName", skillName = "Conductivity" }, { type = "Condition", var = "AffectedByPurityofLightning" }, { type = "GlobalEffect", effectType = "Global", unscalable = true }) }, ["immun[ei]t?y? to curses while you have at least (%d+) rage"] = function(num) return { flag("CurseImmune", { type = "MultiplierThreshold", var = "Rage", threshold = num }) } end, ["you cannot be cursed with silence"] = { flag("SilenceImmune") }, ["unaffected by ignite"] = { mod("SelfIgniteEffect", "MORE", -100) }, From 2777578703bdd0de19368646df7fd4a0606f7095 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 14/44] 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 f102f193c3..528b475797 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 58d19e131ad56bf22504f7510eef6a148f3412cf 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 15/44] 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 528b475797..d164744c0e 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 a6f2a52e4c240b916ae1aa4de76ac5e8e5c688e5 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 16/44] 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 d164744c0e..6bdf84910a 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" }, @@ -943,6 +942,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 @@ -1106,8 +1106,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) @@ -1119,23 +1117,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 74ee6e204ca8324752f489342f16608fbcc1d581 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 17/44] 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 6bdf84910a..cf6aa2aec4 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1118,7 +1118,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 @@ -1129,7 +1129,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() @@ -1138,11 +1138,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 8ae10784a1c9ef187a57822782a6070d79a2c347 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 18/44] 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 cf6aa2aec4..af12bcec93 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 cf16771ddc0b1b7244895ed3ce2a02065c8f2629 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 19/44] 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 a856fd179d..25ef71c1f7 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() @@ -1496,8 +1497,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) @@ -1510,13 +1515,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 = { } @@ -1540,8 +1547,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 @@ -1553,6 +1560,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 @@ -1563,8 +1571,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 @@ -1578,6 +1586,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.") @@ -1642,11 +1651,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 @@ -1671,11 +1680,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 @@ -1699,8 +1708,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 @@ -1708,6 +1716,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 @@ -1753,8 +1762,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 @@ -1813,6 +1821,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 @@ -1987,7 +1996,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 @@ -1998,9 +2006,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() @@ -2010,20 +2019,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 @@ -2032,11 +2069,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 @@ -2052,68 +2092,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)) @@ -2159,10 +2143,17 @@ function TreeTabClass:FindTimelessJewel() end end + local tradeTypes = { + "securable", + "available", + "onlineleague", + "online", + "any" + } local search = { query = { status = { - option = "available" + option = tradeTypes[self.tradeTypeIndex] }, stats = { { @@ -2196,10 +2187,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" @@ -2214,12 +2207,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 @@ -2535,7 +2603,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) @@ -2601,16 +2683,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 22a00d0fb5f3eb8e91c871f46f0a056d74dc43e9 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 20/44] 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 23b5a40164..40762fd997 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 af12bcec93..2556fc9544 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 380033b6408b8cddd19a8004aa3399fcffbf10f5 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 21/44] 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 40762fd997..5ef8acef32 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 2556fc9544..8ca67e6e7c 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 }} @@ -966,6 +940,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 fcb261c9dd464cd7ba74ea00511dc6d5b635b150 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 22/44] 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 013b3bfe791bdef5d17a969433e19dd695556dff 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 23/44] 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 8ca67e6e7c..95da1e5e07 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 8830630026435d7b10017c57a635c159503a6a8e 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 24/44] 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 95da1e5e07..e7566b0f1d 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 b8883669d6822a0d11f9c212690d0dc71003c41f 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 25/44] fix test for poe.ninja currencies --- spec/System/TestTradeQueryCurrency_spec.lua | 26 +++++++++++---------- src/Classes/TradeQuery.lua | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index d3cccb5298..3f29d7e699 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -39,19 +39,21 @@ describe("TradeQuery Currency Conversion", function() end) end) - describe("PriceBuilderProcessPoENinjaResponse", function() - -- Pass: Processes without error, restoring map - -- 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" } - local resp = { exotic = 10 } - mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp) - -- No crash expected - assert.is_true(true) + describe("PriceBuilderProcessPoENinjaResponse", function() + -- 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) + assert.is_true(mock_tradeQuery.controls.pbNotice.label == "No currencies received from PoE Ninja") mock_tradeQuery.currencyConversionTradeMap = orig_conv - end) - end) + end) + end) describe("GetTotalPriceString", function() -- Pass: Sums and formats correctly (e.g., "5 chaos, 10 div") diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index e7566b0f1d..bf64dec1aa 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 f47a74753905f43552cf0ca9a82aa97fa99f5d2b 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 26/44] 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 bf64dec1aa..8bba768617 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 cc7e43b8ecb2b889ed1d080bd1c4b6e071024510 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 27/44] 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 8bba768617..02b47ed243 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 f8812b755b46bfc81f4c5fa34c12581520aac835 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 28/44] 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 02b47ed243..c1a702c727 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 From 24153b4fdee231addfcca5014bf3edd4cdcfb951 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 22:10:23 +1000 Subject: [PATCH 29/44] Fix indents --- spec/System/TestTradeQueryCurrency_spec.lua | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index 3f29d7e699..58fa2eec40 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -39,21 +39,21 @@ describe("TradeQuery Currency Conversion", function() end) end) - describe("PriceBuilderProcessPoENinjaResponse", function() - -- 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" } + describe("PriceBuilderProcessPoENinjaResponse", function() + -- 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) + local resp = { exotic = 10 } + mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp) + -- No crash expected + assert.is_true(true) assert.is_true(mock_tradeQuery.controls.pbNotice.label == "No currencies received from PoE Ninja") mock_tradeQuery.currencyConversionTradeMap = orig_conv - end) - end) + end) + end) describe("GetTotalPriceString", function() -- Pass: Sums and formats correctly (e.g., "5 chaos, 10 div") From 035a3a507b68d4baa82b241f63e7b25f2464bc87 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 22:31:36 +1000 Subject: [PATCH 30/44] Fix failing test Each test now runs with a clean slate as PriceBuilderProcessPoENinjaResponse was being affected by previous tests --- spec/System/TestTradeQueryCurrency_spec.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index 58fa2eec40..9bc1de92c6 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -1,5 +1,9 @@ describe("TradeQuery Currency Conversion", function() - local mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) + local mock_tradeQuery + + before_each(function() + mock_tradeQuery = new("TradeQuery", { itemsTab = {} }) + end) -- test case for commit: "Skip callback on errors to prevent incomplete conversions" describe("FetchCurrencyConversionTable", function() From 8889bdcad15eb56cb66d63694d01846f6d60b999 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 15 Apr 2026 22:37:46 +1000 Subject: [PATCH 31/44] Fix test --- spec/System/TestTradeQueryCurrency_spec.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index 9bc1de92c6..d412af2950 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -49,6 +49,8 @@ describe("TradeQuery Currency Conversion", function() it("handles unmapped currency", function() local orig_conv = mock_tradeQuery.currencyConversionTradeMap mock_tradeQuery.currencyConversionTradeMap = { div = "id" } + mock_tradeQuery.pbLeague = "league" + mock_tradeQuery.pbCurrencyConversion = { league = {} } mock_tradeQuery.controls.pbNotice = { label = ""} local resp = { exotic = 10 } mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp) From 41b1324ec66c4a5e14e35ac1d1200294db09ee39 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 05:34:42 +1000 Subject: [PATCH 32/44] Fix league name and scourge ui anchor pbLeagueRealName does not exist. The correct variable is pbLeague The scourge anchor has a typo in it --- src/Classes/TradeQueryGenerator.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 51cd61d073..2018a6a2f1 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1218,7 +1218,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb --controls.includeSynthesis.state = (self.lastIncludeSynthesis == nil or self.lastIncludeSynthesis == true) local lastItemAnchor = controls.includeCorrupted - local includeScourge = self.queryTab.pbLeagueRealName == "Standard" or self.queryTab.pbLeagueRealName == "Hardcore" + local includeScourge = self.queryTab.pbLeague == "Standard" or self.queryTab.pbLeague == "Hardcore" local function updateLastAnchor(anchor, height) lastItemAnchor = anchor @@ -1239,7 +1239,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.includrecteScourge) + updateLastAnchor(controls.includeScourge) end if isAmuletSlot then From 4f7719111a27db241555e1c9f8f888a7b86be4bc Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 05:36:55 +1000 Subject: [PATCH 33/44] Fix search for item code The trade site uses floats for the weights internally when sorting but only shows integers to the site and api Need to use +-1 so that it doesn't accidently miss an item e.g. before a weight of 100.3 would show as 100 in the api callback and then we'd search for 99.9 - 100.1 so the search would never show the item --- src/Classes/TradeQuery.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index a8fd052511..f66045601e 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1176,12 +1176,11 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro else local exactQuery = dkjson.decode(self.lastQuery) -- 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 } + -- weight on site uses floats but only shows integer in the api + -- e.g. weight of 172.3 shows up as 172 in the api + exactQuery.query.stats[1].value = { min = floor(itemResult.weight, 1) - 1, max = round(itemResult.weight, 1) + 1 } -- also apply trader name. this should make false positives - -- extremely unlikely. this doesn't seem to take up a filter - -- slot + -- 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) From e90500d025af3a368a0b3e29dc07e92d1adee9dc Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 05:38:10 +1000 Subject: [PATCH 34/44] Use last query for manually pasted strings Can now properly search for items when the user pastes a weighted sum search --- src/Classes/TradeQuery.lua | 3 ++- src/Classes/TradeQueryRequests.lua | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index f66045601e..6b789ddcfe 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1038,11 +1038,12 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro controls["priceButton"..row_idx] = new("ButtonControl", { "TOPLEFT", controls["uri"..row_idx], "TOPRIGHT"}, {8, 0, 100, row_height}, "Price Item", function() controls["priceButton"..row_idx].label = "Searching..." - self.tradeQueryRequests:SearchWithURL(controls["uri"..row_idx].buf, function(items, errMsg) + self.tradeQueryRequests:SearchWithURL(controls["uri"..row_idx].buf, function(items, errMsg, query) if errMsg then self:SetNotice(controls.pbNotice, "Error: " .. errMsg) else self:SetNotice(controls.pbNotice, "") + self.lastQuery = query self.resultTbl[row_idx] = items self:UpdateControlsWithItems(row_idx) end diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 180050d106..95f8b6a5ad 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -302,7 +302,7 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) }) end ----@param callback fun(items:table, errMsg:string) +---@param callback fun(items:table, errMsg:string, query:string) function TradeQueryRequestsClass:SearchWithURL(url, callback) local subpath = url:match(self.hostName .. "trade/search/(.+)$") local paths = {} @@ -310,7 +310,7 @@ function TradeQueryRequestsClass:SearchWithURL(url, callback) table.insert(paths, path) end if #paths < 2 or #paths > 3 then - return callback(nil, "Invalid URL") + return callback(nil, "Invalid URL", nil) end local realm, league, queryId if #paths == 3 then @@ -320,9 +320,11 @@ function TradeQueryRequestsClass:SearchWithURL(url, callback) queryId = paths[#paths] self:FetchSearchQueryHTML(realm, league, queryId, function(query, errMsg) if errMsg then - return callback(nil, errMsg) + return callback(nil, errMsg, nil) end - self:SearchWithQuery(realm, league, query, callback) + self:SearchWithQuery(realm, league, query, function(items, searchErrMsg) + callback(items, searchErrMsg, query) + end) end) end From 716fe21679eacafc63096ee9c6b568b9227a5ec6 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 05:45:54 +1000 Subject: [PATCH 35/44] Add any to trade search types --- src/Classes/TradeQuery.lua | 1 + src/Classes/TradeQueryGenerator.lua | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 6b789ddcfe..7deeb2df05 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -288,6 +288,7 @@ on trade site to work on other leagues and realms)]] "Instant buyout and in person", "In person (online in league)", "In person (online)", + "Any (includes offline)" } self.controls.tradeTypeSelection = new("DropDownControl", { "TOPLEFT", self.controls.poesessidButton, "BOTTOMLEFT" }, diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 2018a6a2f1..f4de67f794 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1033,6 +1033,7 @@ function TradeQueryGeneratorClass:FinishQuery() "available", "onlineleague", "online", + "any", } local selectedTradeType = self.tradeTypes[self.tradeTypeIndex] -- Generate trade query str and open in browser From 542a4654d12dde60eb30333153049fd24a5c113a Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:04:38 +1000 Subject: [PATCH 36/44] Spelling mistakes --- src/Classes/TradeQuery.lua | 2 +- src/Classes/TreeTab.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 7deeb2df05..63240db1ab 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -362,7 +362,7 @@ Highest Stat Value - Sort from highest to lowest Stat Value change of equipping 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 + -- 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) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 25ef71c1f7..4b20a55418 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -2268,7 +2268,7 @@ function TreeTabClass:FindTimelessJewel() -- 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 + -- function does not get called when the selection index stays the same controls.realmSelection.selFunc(controls.realmSelection.selIndex) controls.realmLabel = new("LabelControl", { "TOPRIGHT", controls.realmSelection, "TOPLEFT" }, { -labelSpacing, 0, 0, labelHeight }, "^7Realm:") From bd5c6f1a49fbc66e6bb20c989e532f312db9076f Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:04:57 +1000 Subject: [PATCH 37/44] Wrong variable name for control --- src/Classes/TradeQueryGenerator.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index f4de67f794..5b07223b92 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1297,8 +1297,8 @@ Remove: %s will be removed from the search results.]], term, term, term) elseif slot and not isAbyssalJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then local selFunc = function(_index, value) -- influenced items can't have eldritch implicits - if controls.copyEldritchOrEnchant and isEldritchModSlot then - controls.copyEldritchOrEnchant.enabled = value == "None" + if controls.copyEldritch and isEldritchModSlot then + controls.copyEldritch.enabled = value == "None" end end controls.influence1 = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, From c3e5070ba6615b654ddd07d39af8a54939f3254b Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:05:30 +1000 Subject: [PATCH 38/44] More filters incase missing search elements --- 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 63240db1ab..96b02b7244 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1183,7 +1183,10 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro exactQuery.query.stats[1].value = { min = floor(itemResult.weight, 1) - 1, max = round(itemResult.weight, 1) + 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 } } + exactQuery.query.filters = exactQuery.query.filters or { } + exactQuery.query.filters.trade_filters = exactQuery.query.filters.trade_filters or { filters = { } } + exactQuery.query.filters.trade_filters.filters = exactQuery.query.filters.trade_filters.filters or { } + exactQuery.query.filters.trade_filters.filters.account = { input = itemResult.trader } local exactQueryStr = dkjson.encode(exactQuery) From 04925007f9557e38d15e5908d1a89888be6c2927 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:06:05 +1000 Subject: [PATCH 39/44] Issue with newItem.base.weapon bypassed rest of checks --- src/Classes/ItemsTab.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 5ef8acef32..9f1f3fa70c 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -1617,12 +1617,8 @@ function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImpl 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 + 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() From 2de61d9356d92ac68eb9a65223e362aa190b42f0 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:07:54 +1000 Subject: [PATCH 40/44] Fix Timeless jewel league selection resetting --- src/Classes/TreeTab.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 4b20a55418..d32323cfca 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -2248,6 +2248,7 @@ function TreeTabClass:FindTimelessJewel() t_insert(tempLeagueTable, val) end leagueList = copyTable(tempLeagueTable) + self.tradeLeaguesList[currentRealmId] = leagueList else t_insert(leagueList, league) end From d4bb179c6afcaa05f49584e753e8797ab5fd68a6 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 06:09:31 +1000 Subject: [PATCH 41/44] Remove debug console prints --- src/Classes/TradeQuery.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 96b02b7244..aad6e437a3 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -859,7 +859,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)) + --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) @@ -977,7 +977,6 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro -- 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) From dc81dc8ce504d6927bb3b6fe78911cd985c2443c Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 19:16:55 +1000 Subject: [PATCH 42/44] Fix option depending on last changed dropdown The dropdown checked if the last changed dropdown was set to none instead of checking that both of the dropdowns were set to none. Setting an influence and setting it back to none would make the copy eldritch box become selectable when it shouldn't be --- src/Classes/TradeQueryGenerator.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 5b07223b92..2e21816092 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1295,10 +1295,12 @@ Remove: %s will be removed from the search results.]], term, term, term) 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 - local selFunc = function(_index, value) + local selFunc = function() -- influenced items can't have eldritch implicits if controls.copyEldritch and isEldritchModSlot then - controls.copyEldritch.enabled = value == "None" + local hasInfluence1 = controls.influence1 and controls.influence1:GetSelValue() ~= "None" + local hasInfluence2 = controls.influence2 and controls.influence2:GetSelValue() ~= "None" + controls.copyEldritch.enabled = not hasInfluence1 and not hasInfluence2 end end controls.influence1 = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, @@ -1309,6 +1311,7 @@ Remove: %s will be removed from the search results.]], term, term, term) controls.influence2 = new("DropDownControl", { "TOPLEFT", controls.influence1, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, influenceDropdownNames, selFunc) controls.influence2:SetSel(self.lastInfluence2 or 1) + selFunc() controls.influence2Label = new("LabelControl", { "RIGHT", controls.influence2, "LEFT" }, { -5, 0, 0, 16 }, "^7Influence 2:") updateLastAnchor(controls.influence2, 46) From 756390d7fec40a7ca5b314c3025f92cab8302585 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 19:29:50 +1000 Subject: [PATCH 43/44] Fix weapon enchant only copying from weapon slot 1 --- src/Classes/ItemsTab.lua | 4 ++-- src/Classes/TradeQuery.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 9f1f3fa70c..84c5f487b3 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -1585,8 +1585,8 @@ function ItemsTabClass:DeleteItem(item, deferUndoState) end end -function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImplicits, overwrite) - local newItemType = newItem.base.weapon and "Weapon 1" or newItem.base.type +function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImplicits, overwrite, sourceSlotName) + local newItemType = sourceSlotName or (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 diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index aad6e437a3..461c58fbe1 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -979,7 +979,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro self.tradeQueryGenerator.lastCopyEnchantMode == "Copy Current" then for i, _ in ipairs(items) do local item = new("Item", items[i].item_string) - self.itemsTab:CopyAnointsAndEldritchImplicits(item, true, true) + self.itemsTab:CopyAnointsAndEldritchImplicits(item, true, true, context.slotTbl.slotName) items[i].item_string = item:BuildRaw() end elseif self.tradeQueryGenerator.lastCopyEnchantMode == "Remove" then From 3b777f22ec1edb2cc8e77abbcb11d86d09706b18 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 16 Apr 2026 19:41:22 +1000 Subject: [PATCH 44/44] Comments --- src/Classes/TradeQuery.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 461c58fbe1..793dd7822c 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -362,9 +362,7 @@ Highest Stat Value - Sort from highest to lowest Stat Value change of equipping 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 - -- initialised - -- https://github.com/PathOfBuildingCommunity/PathOfBuilding/issues/9678 + -- avoid calling selFunc to avoid updating controls before they are initialised self.controls.itemSortSelection:SetSel(self.pbItemSortSelectionIndex, true) self.controls.itemSortSelectionLabel = new("LabelControl", {"TOPRIGHT", self.controls.itemSortSelection, "TOPLEFT"}, {-4, 0, 56, 16}, "^7Sort By:")