Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions spec/System/TestItemMods_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -930,4 +930,134 @@ describe("TetsItemMods", function()
assert.is_truthy(tooltip.lines[#tooltip.lines].text:find("Item base is not supported", 1, true))
end)

local function findActiveSkill(name)
for _, activeSkill in ipairs(build.calcsTab.mainEnv.player.activeSkillList) do
if activeSkill.activeEffect.grantedEffect.name == name then
return activeSkill
end
end
end

local function countSupport(activeSkill, supportId)
local count = 0
for _, effect in ipairs(activeSkill.effectList) do
if effect.grantedEffect.id == supportId then
count = count + 1
end
end
return count
end

local function hasSupport(activeSkill, supportId)
return countSupport(activeSkill, supportId) > 0
end

local function addItem(raw)
build.itemsTab:CreateDisplayItemFromRaw(raw)
build.itemsTab:AddDisplayItem()
end

-- Pearl of Tsoatha: despite the "Socketed in" wording these support skills granted
-- by the item in that slot as well, which ExtraSupport normally excludes
it("ExtraSupport marked appliesToGrantedSkills supports an item granted skill", function()
addItem("Granting Helmet\nEzomyte Burgonet\nGrants Level 20 Fireball")
addItem("Supporting Ring\nPrismatic Ring\nSkills Socketed in your Helmet are Supported by level 20 Added Lightning Damage")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_true(hasSupport(fireball, "SupportAddedLightningDamage"))
assert.is_true(fireball.skillModList:Sum("BASE", fireball.skillCfg, "LightningMin") > 0)
end)

it("ExtraSupport marked appliesToGrantedSkills still supports a socketed gem", function()
addItem("Socketed Helmet\nEzomyte Burgonet\nSockets: B")
addItem("Supporting Ring\nPrismatic Ring\nSkills Socketed in your Helmet are Supported by level 20 Added Lightning Damage")
build.skillsTab:PasteSocketGroup("Slot: Helmet\nFireball 20/0 1\n")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_true(hasSupport(fireball, "SupportAddedLightningDamage"))
end)

it("ExtraSupport marked appliesToGrantedSkills does not cross slots", function()
addItem("Granting Gloves\nSpiked Gloves\nGrants Level 20 Fireball")
addItem("Supporting Ring\nPrismatic Ring\nSkills Socketed in your Helmet are Supported by level 20 Added Lightning Damage")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_false(hasSupport(fireball, "SupportAddedLightningDamage"))
end)

-- The relaxation is opt-in, so a Forbidden Shako style mod must keep the old behaviour
it("plain ExtraSupport does not support an item granted skill", function()
addItem("Shako Like Helmet\nEzomyte Burgonet\nGrants Level 20 Fireball\nSocketed Gems are Supported by Level 20 Added Cold Damage")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_false(hasSupport(fireball, "SupportAddedColdDamage"))
end)

local anointedTreeSkill = "Anointed Amulet\nAmber Amulet\nAllocates Radiant Crusade"
local treePearl = "Tree Pearl\nPrismatic Ring\nSkills granted by your Passive Tree are Supported by level 20 Minion Damage"

it("ExtraSupport marked appliesToGrantedSkills supports a tree granted skill", function()
addItem(anointedTreeSkill)
addItem(treePearl)
runCallback("OnFrame")

local sentinel = findActiveSkill("Summon Sentinel of Radiance")
assert.is_not_nil(sentinel)
assert.is_true(hasSupport(sentinel, "SupportMinionDamage"))
end)

-- Regression guard for the duplicate application fix: addExtraSupports has to go
-- through addBestSupport, otherwise both rings insert their own copy of the support
it("applies a tree ExtraSupport once when two items grant it", function()
addItem(anointedTreeSkill)
addItem(treePearl)
addItem(treePearl)
runCallback("OnFrame")

local sentinel = findActiveSkill("Summon Sentinel of Radiance")
assert.is_not_nil(sentinel)
assert.are.equals(1, countSupport(sentinel, "SupportMinionDamage"))
end)

it("tree ExtraSupport does not support an item granted skill", function()
addItem("Granting Helmet\nEzomyte Burgonet\nGrants Level 20 Fireball")
addItem("Tree Pearl\nPrismatic Ring\nSkills granted by your Passive Tree are Supported by level 20 Added Lightning Damage")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_false(hasSupport(fireball, "SupportAddedLightningDamage"))
end)

it("tree ExtraSupport does not support a socketed gem", function()
addItem("Socketed Helmet\nEzomyte Burgonet\nSockets: B")
addItem("Tree Pearl\nPrismatic Ring\nSkills granted by your Passive Tree are Supported by level 20 Added Lightning Damage")
build.skillsTab:PasteSocketGroup("Slot: Helmet\nFireball 20/0 1\n")
runCallback("OnFrame")

local fireball = findActiveSkill("Fireball")
assert.is_not_nil(fireball)
assert.is_false(hasSupport(fireball, "SupportAddedLightningDamage"))
end)

-- Every tree granted skill shares the synthetic "Passive Tree" slot, so each one pools
-- the support lists of all the others. A second tree skill must not inflate the count.
it("applies a tree ExtraSupport once when another tree skill shares the slot", function()
addItem("Anointed Amulet\nAmber Amulet\nAllocates Radiant Crusade\nAllocates Avatar of the Wilds")
addItem(treePearl)
runCallback("OnFrame")

assert.is_not_nil(findActiveSkill("Unbound Avatar"))
local sentinel = findActiveSkill("Summon Sentinel of Radiance")
assert.is_not_nil(sentinel)
assert.are.equals(1, countSupport(sentinel, "SupportMinionDamage"))
end)
end)
20 changes: 19 additions & 1 deletion src/Classes/TradeQuery.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function TradeQueryClass:TradeQuery(itemsTab)
self.lastComparedWeightList = { }

-- default set of trade item sort selection
---@type TradeQuerySlotTable[]
self.slotTables = { }
self.pbItemSortSelectionIndex = 1
-- for each realm and league, a table of values of each currency in div
Expand Down Expand Up @@ -531,6 +532,15 @@ Highest Weight - Displays the order retrieved from trade]]
end

-- Individual slot rows
---@class TradeQuerySlotTable
---@field slotName string Display name of the row, also the slot name for regular slots
---@field fullName string? Actual slot name for abyssal sockets, where slotName is the shortened label
---@field nodeId number? Passive tree node id for jewel socket rows
---@field unique boolean? Row targets a specific unique instead of a slot
---@field alreadyCorrupted boolean? The targeted unique only drops corrupted
---@field selectedJewelNodeId number? Jewel socket the unique row searches for

---@type TradeQuerySlotTable[]
local slotTables = {}
for _, slotName in ipairs(baseSlots) do
if self.itemsTab.slots[slotName].shown() then
Expand Down Expand Up @@ -602,7 +612,15 @@ Highest Weight - Displays the order retrieved from trade]]
self.controls["name"..row_count].shown = function()
return hideRowFunc(self, row_count)
end
row_count = row_count + 1

-- Pearl of Tsoatha
self.slotTables[row_count] = { slotName = "Pearl of Tsoatha", unique = true }
self:PriceItemRowDisplay(row_count, top_pane_alignment_ref, row_vertical_padding, row_height)
self.controls["name" .. row_count].y = self.controls["name" .. row_count].y + (row_height + row_vertical_padding)
self.controls["name" .. row_count].shown = function()
return hideRowFunc(self, row_count)
end
-- fix case where the row count is reduced from the last time the popup was
-- opened, which would leave extra row controls in the menu
for k, v in pairs(self.controls) do
Expand Down Expand Up @@ -1153,7 +1171,7 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite
local isSearching = controls["priceButton"..row_idx].label == "Searching..."
local selectedJewelSlot = slotTbl.selectedJewelNodeId and self.itemsTab.sockets[slotTbl.selectedJewelNodeId]
local hasRequiredJewelSlot = not slotTbl.unique or selectedJewelSlot and not selectedJewelSlot.inactive
return isAuthorized and validURL and not isSearching and hasRequiredJewelSlot
return isAuthorized and validURL and not isSearching and (hasRequiredJewelSlot or not slotTbl.selectedJewelNodeId)
end
controls["priceButton"..row_idx].tooltipFunc = function(tooltip)
tooltip:Clear()
Expand Down
79 changes: 72 additions & 7 deletions src/Classes/TradeQueryGenerator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ local function getStatEntries(modType)
["AgainstTheDarkness"] = "explicit",
["pseudo"] = "pseudo",
["Enchant"] = "enchant",
["Pearl"] = "explicit",
}
if tradeStatCategoryIndices[modType] then
for _, cat in ipairs(tradeStats) do
Expand Down Expand Up @@ -156,9 +157,11 @@ end
---@class TradeQueryGenerator
local TradeQueryGeneratorClass = newClass("TradeQueryGenerator")

---@param queryTab TradeQuery
function TradeQueryGeneratorClass:TradeQueryGenerator(queryTab)
self:InitMods()
self.queryTab = queryTab
---@type ItemsTab
self.itemsTab = queryTab.itemsTab
self.calcContext = { }
self.lastMaxPrice = nil
Expand Down Expand Up @@ -419,15 +422,15 @@ function TradeQueryGeneratorClass:InitMods()
local description = "This file contains the trade site data from https://www.pathofexile.com/api/trade/data/stats"
utils.saveTableToFile("./Data/TradeSiteStats.lua", body.result, description)
self.modData = {
["Explicit"] = { },
["Implicit"] = { },
["Explicit"] = {},
["Implicit"] = {},
["Enchant"] = {},
["Corrupted"] = { },
["Scourge"] = { },
["Eater"] = { },
["Corrupted"] = {},
["Scourge"] = {},
["Eater"] = {},
["Exarch"] = {},
["PassiveNode"] = { },
["WatchersEye"] = { },
["PassiveNode"] = {},
["WatchersEye"] = {},
}

local tradeQueryStatsParsed = body
Expand Down Expand Up @@ -704,6 +707,18 @@ function TradeQueryGeneratorClass:StartQuery(slot, options)
itemCategory = "AnyJewel"
itemCategoryQueryStr = "jewel"
end
if options.special.itemName == "Pearl of Tsoatha" then
itemCategory = "Ring"
itemCategoryQueryStr = "ring"
testItemType = "Prismatic Ring"
special = {
queryFilters = {},
queryExtra = {
name = "Pearl of Tsoatha",
},
pearl = true,
}
end
else
itemCategoryQueryStr, itemCategory = tradeHelpers.getTradeCategory(slot.slotName, existingItem)

Expand Down Expand Up @@ -781,6 +796,38 @@ function TradeQueryGeneratorClass:ExecuteQuery()
end
return
end
if self.calcContext.special.pearl then
-- this item has no queryMods data as it only has a single mod which
-- instead has stats defining what it does
local mods = {}
-- mystery hash. no clue what stat names this is from
local statHash = "4089743927"
for slot = 1, 4 do
for support = 1, #require("Data.PearlSupports") do
local stats = {
local_pearl_random_support_gem_2_level = 20,
local_pearl_random_support_gem_2_slot_index = slot,
local_pearl_random_support_gem_2_index = support,
}
local text = table.concat(data.describeStats(stats, "stat_descriptions"), " ")
local modEntry = {
Ring = {
max = 1,
min = 1,
},
specialCaseData = {},
sign = "",
tradeMod = {
id = string.format("explicit.stat_%s|%s|%s", statHash, slot, support),
text = text
},
}
table.insert(mods, modEntry)
end
end
self:GenerateModWeights(mods)
return
end
self:GenerateModWeights(self.modData["Explicit"])
self:GenerateModWeights(self.modData["Implicit"])
if self.calcContext.options.includeCorrupted then
Expand Down Expand Up @@ -1177,6 +1224,21 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb
end
updateLastAnchor(controls.jewelSlot)
end
if context.slotTbl.slotName == "Pearl of Tsoatha" then
local ringList = {}
for i = 1, 3 do
local slot = self.itemsTab.slots["Ring " .. i]
if slot and slot.shown() then
table.insert(ringList, slot)
end
end
table.sort(ringList, function(a, b)
return a.label < b.label
end)
controls.ringSlot = new("DropDownControl"):DropDownControl({ "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 100, 18 }, ringList, function() end)
controls.ringSlotLabel = new("LabelControl"):LabelControl({ "RIGHT", controls.ringSlot, "LEFT" }, { -5, 0, 0, 16 }, "Ring Slot:")
updateLastAnchor(controls.ringSlot)
end
-- these unique items cannot be mirrored
if not context.slotTbl.unique then
controls.includeMirrored = new("CheckBoxControl"):CheckBoxControl({"TOPRIGHT",lastItemAnchor,"BOTTOMRIGHT"}, {0, 5, 18}, "Mirrored Items:", function(state) end)
Expand Down Expand Up @@ -1403,6 +1465,9 @@ Remove: %s will be removed from the search results.]], term, term, term)
slot = controls.jewelSlot:GetSelValue()
context.slotTbl.selectedJewelNodeId = slot.nodeId
end
if controls.ringSlot then
slot = controls.ringSlot:GetSelValue()
end

self:StartQuery(slot, options)
end)
Expand Down
Loading
Loading