Skip to content
Merged
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
116 changes: 116 additions & 0 deletions spec/System/TestImportTab_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,119 @@ describe("ImportTab", function()
assert.True(importTab.controls.charSelect.list[1].detail:match("Future Ascendancy") ~= nil)
end)
end)

describe("ImportTab quest reward import", function()
before_each(function()
newBuild()
end)

local function findQuest(info)
for _, quest in ipairs(data.questRewards) do
if quest.Info == info then
return "quest" .. quest.Description .. quest.Area .. quest.Info, quest
end
end
error("quest reward not found for Info: " .. info)
end

local function importStats(questStats)
build.importTab:ImportQuestRewardConfig(questStats)
return build.configTab.input
end

it("decomposes 0.5 rolled-up totals across the contributing quests", function()
local input = importStats({
"+20 to maximum Life",
"+60 to [Spirit|Spirit]",
"+15% to [Resistances|Cold Resistance]",
"+15% to [Resistances|Fire Resistance]",
"+15% to [Resistances|Lightning Resistance]",
})

assert.is_true(input[(findQuest("Candlemass"))])
assert.is_true(input[(findQuest("King In The Mists"))])
assert.is_true(input[(findQuest("Ignagduk"))])
assert.is_true(input[(findQuest("Beira"))])
assert.is_true(input[(findQuest("Blackjaw"))])
assert.is_true(input[(findQuest("Sisters of Garukhan Shrine"))])

local tasalioVar, tasalio = findQuest("Tasalio's Test")
assert.are.equals(tasalio.Options[2], input[tasalioVar])
local ngamahuVar, ngamahu = findQuest("Ngamahu's Test")
assert.are.equals(ngamahu.Options[2], input[ngamahuVar])
local tawhoaVar, tawhoa = findQuest("Tawhoa's Test")
assert.are.equals(tawhoa.Options[2], input[tawhoaVar])

assert.is_false(input[(findQuest("Lythara"))])
end)

it("sums duplicate 0.4 per-line Spirit rewards the same as the 0.5 summed total", function()
local input = importStats({
"+30 to [Spirit|Spirit]",
"+30 to [Spirit|Spirit]",
"+40 to [Spirit|Spirit]",
})
assert.is_true(input[(findQuest("King In The Mists"))])
assert.is_true(input[(findQuest("Ignagduk"))])
assert.is_true(input[(findQuest("Lythara"))])
end)

it("ticks exactly one of the interchangeable +30 Spirit quests when only one is claimed", function()
local input = importStats({ "+30 to [Spirit|Spirit]" })
local king = input[(findQuest("King In The Mists"))]
local ignagduk = input[(findQuest("Ignagduk"))]
assert.is_false(input[(findQuest("Lythara"))])
assert.are.equals(1, (king and 1 or 0) + (ignagduk and 1 or 0))
end)

it("disambiguates Spirit total 70 to one +30 plus Lythara, not 30+30", function()
local input = importStats({ "+70 to [Spirit|Spirit]" })
assert.is_true(input[(findQuest("Lythara"))])
local king = input[(findQuest("King In The Mists"))]
local ignagduk = input[(findQuest("Ignagduk"))]
assert.are.equals(1, (king and 1 or 0) + (ignagduk and 1 or 0))
end)

it("disambiguates Spirit total 40 to Lythara alone, not a +30", function()
local input = importStats({ "+40 to [Spirit|Spirit]" })
assert.is_true(input[(findQuest("Lythara"))])
assert.is_false(input[(findQuest("King In The Mists"))])
assert.is_false(input[(findQuest("Ignagduk"))])
end)

it("selects the correct multi-line option and leaves unclaimed option quests at None", function()
local medallionVar, medallion = findQuest("Medallion")
local input = importStats({
"30% increased [Charm] Effect Duration",
"+1 [Charm] Slot",
})
assert.are.equals(medallion.Options[2], input[medallionVar])
assert.are.equals("None", input[(findQuest("Seven Pillars"))])
assert.is_false(input[(findQuest("Beira"))])
end)

it("credits Tribal Medicine's Kaom option, not Seven Pillars which shares the stat at 15%", function()
local tribalVar, tribal = findQuest("Tribal Medicine")
local input = importStats({ "30% increased Global [Armour], [Evasion] and [EnergyShield|Energy Shield]" })
assert.are.equals(tribal.Options[1], input[tribalVar])
assert.are.equals("None", input[(findQuest("Seven Pillars"))])
end)

it("selects Tribal Medicine's Rakiata multi-stat option", function()
local tribalVar, tribal = findQuest("Tribal Medicine")
local input = importStats({
"+15% of [Armour|Armour] also applies to [ElementalDamage|Elemental Damage]",
"Gain [Deflect|Deflection Rating] equal to 12% of [Evasion|Evasion Rating]",
"12% [FasterESRechargeStart|faster start of Energy Shield Recharge]",
})
assert.are.equals(tribal.Options[2], input[tribalVar])
end)

it("decomposes Global Armour, Evasion and Energy Shield shared by Tribal Medicine (30%) and Seven Pillars (15%)", function()
local tribalVar, tribal = findQuest("Tribal Medicine")
local sevenVar, seven = findQuest("Seven Pillars")
local input = importStats({ "45% increased Global [Armour], [Evasion] and [EnergyShield|Energy Shield]" })
assert.are.equals(tribal.Options[1], input[tribalVar])
assert.are.equals(seven.Options[3], input[sevenVar])
end)
end)
63 changes: 41 additions & 22 deletions src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
--
local ipairs = ipairs
local t_insert = table.insert
local t_remove = table.remove
local b_rshift = bit.rshift
local band = bit.band
local m_max = math.max
local dkjson = require "dkjson"

local tradeHelpers = LoadModule("Classes/TradeHelpers")

local realmList = {
{ label = "PoE2", id = "PoE2", realmCode = "poe2", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" },
}
Expand Down Expand Up @@ -646,39 +647,57 @@ end

function ImportTabClass:ImportQuestRewardConfig(questStats)
local configTab = self.build.configTab
local statLines = {}

-- Reduce a stat line to a numberless key + value (e.g. "+30 to [Spirit|Spirit]" -> "+# to spirit", 30)
local function statKey(text)
text = escapeGGGString(text):lower():gsub("^%s+", ""):gsub("%s+$", "")
return tradeHelpers.modLineTemplate(text), tradeHelpers.modLineValue(text) or 0
end

local statTotals = {}
for _, stat in ipairs(questStats) do
t_insert(statLines, escapeGGGString(stat):lower())
local key, value = statKey(stat)
statTotals[key] = (statTotals[key] or 0) + value
end

-- Stats shared by 3+ quests can't be split greedily (two +30 Spirit quests make 40/70 ambiguous),
-- so resolve those by exact total then zero their totals.
local disambiguation = {
["+# to spirit"] = {
[30] = { "King In The Mists" },
[40] = { "Lythara" },
[60] = { "King In The Mists", "Ignagduk" },
[70] = { "King In The Mists", "Lythara" },
[100] = { "King In The Mists", "Ignagduk", "Lythara" },
},
}
local resolved = {}
for stat, byTotal in pairs(disambiguation) do
local taken = byTotal[statTotals[stat] or 0]
if taken then
for _, info in ipairs(taken) do resolved[info] = true end
statTotals[stat] = 0
end
end

local function splitLine(text)
local out = {}
for line in text:gmatch("[^\r\n]+") do
line = line:gsub("^%s+", ""):lower()
t_insert(out, line)
for line in tostring(text):gmatch("[^\r\n]+") do
local key, value = statKey(line)
t_insert(out, { key = key, value = value })
end
return out
end

-- Ensure all required lines exist, then remove them so they can't match again
-- True if the totals still hold every line of the reward; consume them so a later quest can't reclaim it
local function matchQuest(requiredLines)
local indices = {}
for _, needed in ipairs(requiredLines) do
local found
for idx, line in ipairs(statLines) do
if line == needed then
found = idx
break
end
end
if not found then
for _, line in ipairs(requiredLines) do
if (statTotals[line.key] or 0) < line.value then
return false
end
t_insert(indices, found)
end
table.sort(indices, function(a, b) return a > b end)
for _, idx in ipairs(indices) do
t_remove(statLines, idx)
for _, line in ipairs(requiredLines) do
statTotals[line.key] = statTotals[line.key] - line.value
end
return true
end
Expand All @@ -688,7 +707,7 @@ function ImportTabClass:ImportQuestRewardConfig(questStats)
if quest.useConfig == true then
local var = "quest" .. quest.Description .. quest.Area .. quest.Info
if quest.Stat then
local matches = matchQuest(splitLine(quest.Stat))
local matches = resolved[quest.Info] or matchQuest(splitLine(quest.Stat))
if configTab.input[var] ~= matches then
configTab.input[var] = matches
updated = true
Expand Down
16 changes: 14 additions & 2 deletions src/Data/QuestRewards.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ return {
["AreaLevel"] = 51,
["useConfig"] = true
},
{
["Act"] = 4,
["Description"] = "Act 4",
["Area"] = "Eye of Hinekora",
["Info"] = "Tribal Medicine",
["Options"] = {
"30% increased Global Armour, Evasion and Energy Shield",
"+15% of Armour also applies to Elemental Damage\n\tGain Deflection Rating equal to 12% of Evasion Rating\n\t12% faster start of Energy Shield Recharge",
},
["AreaLevel"] = 51,
["useConfig"] = true
},
{
["Act"] = 4,
["Description"] = "Act 4",
Expand Down Expand Up @@ -252,11 +264,11 @@ return {
["Options"] = {
"+5% to all Elemental Resistances",
"3% increased Movement Speed",
"15% increased Global Defences",
"15% increased Global Armour, Evasion and Energy Shield",
"20% increased Presence Area Of Effect",
"12% increased Cooldown Recovery Rate",
"+5 to all Attributes",
"5% increased Experience Gain\n\t-5% to Elemental Resistances\n\t3% reduced Movement Speed\n\t15% reduced Global Defences\n\t20% reduced Presence Area Of Effect\n\t12% reduced Cooldown Recovery Rate\n\t5% reduced Attributes",
"5% increased Experience Gain\n\t-5% to all Elemental Resistances\n\t3% reduced Movement Speed\n\t15% reduced Global Armour, Evasion and Energy Shield\n\t20% reduced Presence Area Of Effect\n\t12% reduced Cooldown Recovery Rate\n\t5% reduced Attributes",
},
["AreaLevel"] = 63,
["useConfig"] = true
Expand Down
Loading