Skip to content

Commit 8e69c40

Browse files
committed
Add support for importing loadouts from build XML
Adds support for importing only loadouts (item sets, skill sets, and tree specs) from a build XML to current build without overwriting anything else. Signed-off-by: Tomas Slusny <[email protected]>
1 parent 18272f6 commit 8e69c40

File tree

5 files changed

+195
-59
lines changed

5 files changed

+195
-59
lines changed

src/Classes/ImportTab.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ You can get this from your web browser's cookies while logged into the Path of E
314314
self.build:Init(self.build.dbFileName, self.build.buildName, self.importCodeXML, false, self.importCodeSite and self.controls.importCodeIn.buf or nil)
315315
self.build.viewMode = "TREE"
316316
end)
317+
elseif self.controls.importCodeMode.selIndex == 3 then
318+
self.build:ImportLoadouts(self.importCodeXML)
317319
else
318320
self.build:Shutdown()
319321
self.build:Init(false, "Imported build", self.importCodeXML, false, self.importCodeSite and self.controls.importCodeIn.buf or nil)
@@ -331,7 +333,7 @@ You can get this from your web browser's cookies while logged into the Path of E
331333
self.controls.importCodeState.label = function()
332334
return self.importCodeDetail or ""
333335
end
334-
self.controls.importCodeMode = new("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, {0, 4, 160, 20}, { "Import to this build", "Import to a new build" })
336+
self.controls.importCodeMode = new("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, {0, 4, 160, 20}, { "Import to this build", "Import to a new build", "Import loadouts only" })
335337
self.controls.importCodeMode.enabled = function()
336338
return self.build.dbFileName and self.importCodeValid
337339
end
@@ -1199,4 +1201,4 @@ function ImportTabClass:SetPredefinedBuildName()
11991201
local charData = charSelect.list[charSelect.selIndex].char
12001202
local charName = charData.name
12011203
main.predefinedBuildName = accountName.." - "..charName
1202-
end
1204+
end

src/Classes/ItemsTab.lua

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -943,15 +943,23 @@ holding Shift will put it in the second.]])
943943
self.lastSlot = self.slots[baseSlots[#baseSlots]]
944944
end)
945945

946-
function ItemsTabClass:Load(xml, dbFileName)
947-
self.activeItemSetId = 0
948-
self.itemSets = { }
949-
self.itemSetOrderList = { }
950-
self.tradeQuery.statSortSelectionList = { }
946+
function ItemsTabClass:Load(xml, dbFileName, appendItems)
947+
if not appendItems then
948+
self.activeItemSetId = 0
949+
self.itemSets = { }
950+
self.itemSetOrderList = { }
951+
self.tradeQuery.statSortSelectionList = { }
952+
end
953+
954+
local itemIdMap = { }
951955
for _, node in ipairs(xml) do
952956
if node.elem == "Item" then
953957
local item = new("Item", "")
954-
item.id = tonumber(node.attrib.id)
958+
local itemId = tonumber(node.attrib.id)
959+
if not appendItems then
960+
item.id = itemId
961+
end
962+
955963
item.variant = tonumber(node.attrib.variant)
956964
if node.attrib.variantAlt then
957965
item.hasAltVariant = true
@@ -994,8 +1002,13 @@ function ItemsTabClass:Load(xml, dbFileName)
9941002
end
9951003
if item.base then
9961004
item:BuildModList()
997-
self.items[item.id] = item
998-
t_insert(self.itemOrderList, item.id)
1005+
if appendItems then
1006+
self:AddItem(item, true)
1007+
itemIdMap[itemId] = item.id
1008+
else
1009+
self.items[item.id] = item
1010+
t_insert(self.itemOrderList, item.id)
1011+
end
9991012
end
10001013
-- Below is OBE and left for legacy compatibility (all Slots are part of ItemSets now)
10011014
elseif node.elem == "Slot" then
@@ -1008,14 +1021,18 @@ function ItemsTabClass:Load(xml, dbFileName)
10081021
end
10091022
end
10101023
elseif node.elem == "ItemSet" then
1011-
local itemSet = self:NewItemSet(tonumber(node.attrib.id))
1024+
local itemSet = self:NewItemSet(not appendItems and tonumber(node.attrib.id) or nil)
10121025
itemSet.title = node.attrib.title
10131026
itemSet.useSecondWeaponSet = node.attrib.useSecondWeaponSet == "true"
10141027
for _, child in ipairs(node) do
10151028
if child.elem == "Slot" then
10161029
local slotName = child.attrib.name or ""
10171030
if itemSet[slotName] then
1018-
itemSet[slotName].selItemId = tonumber(child.attrib.itemId)
1031+
local itemId = tonumber(child.attrib.itemId)
1032+
if appendItems and itemIdMap[itemId] then
1033+
itemId = itemIdMap[itemId]
1034+
end
1035+
itemSet[slotName].selItemId = itemId
10191036
itemSet[slotName].active = child.attrib.active == "true"
10201037
itemSet[slotName].pbURL = child.attrib.itemPbURL or ""
10211038
end
@@ -1036,16 +1053,20 @@ function ItemsTabClass:Load(xml, dbFileName)
10361053
end
10371054
end
10381055
end
1039-
if not self.itemSetOrderList[1] then
1040-
self.activeItemSet = self:NewItemSet(1)
1041-
self.activeItemSet.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true"
1042-
self.itemSetOrderList[1] = 1
1043-
end
1044-
self:SetActiveItemSet(tonumber(xml.attrib.activeItemSet) or 1)
1045-
if xml.attrib.showStatDifferences then
1046-
self.showStatDifferences = xml.attrib.showStatDifferences == "true"
1056+
if not appendItems then
1057+
if not self.itemSetOrderList[1] then
1058+
self.activeItemSet = self:NewItemSet(1)
1059+
self.activeItemSet.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true"
1060+
self.itemSetOrderList[1] = 1
1061+
end
1062+
self:SetActiveItemSet(tonumber(xml.attrib.activeItemSet) or 1)
1063+
if xml.attrib.showStatDifferences then
1064+
self.showStatDifferences = xml.attrib.showStatDifferences == "true"
1065+
end
1066+
self:ResetUndo()
1067+
else
1068+
return itemIdMap
10471069
end
1048-
self:ResetUndo()
10491070
end
10501071

10511072
function ItemsTabClass:Save(xml)

src/Classes/SkillsTab.lua

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -373,33 +373,36 @@ function SkillsTabClass:LoadSkill(node, skillSetId)
373373
t_insert(self.skillSets[skillSetId].socketGroupList, socketGroup)
374374
end
375375

376-
function SkillsTabClass:Load(xml, fileName)
377-
self.activeSkillSetId = 0
378-
self.skillSets = { }
379-
self.skillSetOrderList = { }
380-
-- Handle legacy configuration settings when loading `defaultGemLevel`
381-
if xml.attrib.matchGemLevelToCharacterLevel == "true" then
382-
self.controls.defaultLevel:SelByValue("characterLevel", "gemLevel")
383-
elseif type(xml.attrib.defaultGemLevel) == "string" and tonumber(xml.attrib.defaultGemLevel) == nil then
384-
self.controls.defaultLevel:SelByValue(xml.attrib.defaultGemLevel, "gemLevel")
385-
else
386-
self.controls.defaultLevel:SelByValue("normalMaximum", "gemLevel")
387-
end
388-
self.defaultGemLevel = self.controls.defaultLevel:GetSelValueByKey("gemLevel")
389-
self.defaultGemQuality = m_max(m_min(tonumber(xml.attrib.defaultGemQuality) or 0, 23), 0)
390-
self.controls.defaultQuality:SetText(self.defaultGemQuality or "")
391-
if xml.attrib.sortGemsByDPS then
392-
self.sortGemsByDPS = xml.attrib.sortGemsByDPS == "true"
393-
end
394-
self.controls.sortGemsByDPS.state = self.sortGemsByDPS
395-
if xml.attrib.showAltQualityGems then
396-
self.showAltQualityGems = xml.attrib.showAltQualityGems == "true"
397-
end
398-
self.controls.showAltQualityGems.state = self.showAltQualityGems
399-
self.controls.showSupportGemTypes:SelByValue(xml.attrib.showSupportGemTypes or "ALL", "show")
400-
self.controls.sortGemsByDPSFieldControl:SelByValue(xml.attrib.sortGemsByDPSField or "CombinedDPS", "type")
401-
self.showSupportGemTypes = self.controls.showSupportGemTypes:GetSelValueByKey("show")
402-
self.sortGemsByDPSField = self.controls.sortGemsByDPSFieldControl:GetSelValueByKey("type")
376+
function SkillsTabClass:Load(xml, fileName, appendSkills)
377+
if not appendSkills then
378+
self.activeSkillSetId = 0
379+
self.skillSets = { }
380+
self.skillSetOrderList = { }
381+
-- Handle legacy configuration settings when loading `defaultGemLevel`
382+
if xml.attrib.matchGemLevelToCharacterLevel == "true" then
383+
self.controls.defaultLevel:SelByValue("characterLevel", "gemLevel")
384+
elseif type(xml.attrib.defaultGemLevel) == "string" and tonumber(xml.attrib.defaultGemLevel) == nil then
385+
self.controls.defaultLevel:SelByValue(xml.attrib.defaultGemLevel, "gemLevel")
386+
else
387+
self.controls.defaultLevel:SelByValue("normalMaximum", "gemLevel")
388+
end
389+
self.defaultGemLevel = self.controls.defaultLevel:GetSelValueByKey("gemLevel")
390+
self.defaultGemQuality = m_max(m_min(tonumber(xml.attrib.defaultGemQuality) or 0, 23), 0)
391+
self.controls.defaultQuality:SetText(self.defaultGemQuality or "")
392+
if xml.attrib.sortGemsByDPS then
393+
self.sortGemsByDPS = xml.attrib.sortGemsByDPS == "true"
394+
end
395+
self.controls.sortGemsByDPS.state = self.sortGemsByDPS
396+
if xml.attrib.showAltQualityGems then
397+
self.showAltQualityGems = xml.attrib.showAltQualityGems == "true"
398+
end
399+
self.controls.showAltQualityGems.state = self.showAltQualityGems
400+
self.controls.showSupportGemTypes:SelByValue(xml.attrib.showSupportGemTypes or "ALL", "show")
401+
self.controls.sortGemsByDPSFieldControl:SelByValue(xml.attrib.sortGemsByDPSField or "CombinedDPS", "type")
402+
self.showSupportGemTypes = self.controls.showSupportGemTypes:GetSelValueByKey("show")
403+
self.sortGemsByDPSField = self.controls.sortGemsByDPSFieldControl:GetSelValueByKey("type")
404+
end
405+
403406
for _, node in ipairs(xml) do
404407
if node.elem == "Skill" then
405408
-- Old format, initialize skill sets if needed
@@ -411,16 +414,18 @@ function SkillsTabClass:Load(xml, fileName)
411414
end
412415

413416
if node.elem == "SkillSet" then
414-
local skillSet = self:NewSkillSet(tonumber(node.attrib.id))
417+
local skillSet = self:NewSkillSet(not appendSkills and tonumber(node.attrib.id) or nil)
415418
skillSet.title = node.attrib.title
416419
t_insert(self.skillSetOrderList, skillSet.id)
417420
for _, subNode in ipairs(node) do
418421
self:LoadSkill(subNode, skillSet.id)
419422
end
420423
end
421424
end
422-
self:SetActiveSkillSet(tonumber(xml.attrib.activeSkillSet) or 1)
423-
self:ResetUndo()
425+
if not appendSkills then
426+
self:SetActiveSkillSet(tonumber(xml.attrib.activeSkillSet) or 1)
427+
self:ResetUndo()
428+
end
424429
end
425430

426431
function SkillsTabClass:Save(xml)

src/Classes/TreeTab.lua

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,11 @@ function TreeTabClass:GetSpecList()
444444
return newSpecList
445445
end
446446

447-
function TreeTabClass:Load(xml, dbFileName)
448-
self.specList = { }
447+
function TreeTabClass:Load(xml, dbFileName, appendSpecs, itemIdMap)
448+
if not appendSpecs then
449+
self.specList = { }
450+
end
451+
449452
if xml.elem == "Spec" then
450453
-- Import single spec from old build
451454
self.specList[1] = new("PassiveSpec", self.build, defaultTreeVersion)
@@ -461,16 +464,35 @@ function TreeTabClass:Load(xml, dbFileName)
461464
main:OpenMessagePopup("Unknown Passive Tree Version", "The build you are trying to load uses an unrecognised version of the passive skill tree.\nYou may need to update the program before loading this build.")
462465
return true
463466
end
467+
468+
-- Remap jewel socket item IDs if appending
469+
if appendSpecs and itemIdMap then
470+
for _, child in ipairs(node) do
471+
if child.elem == "Sockets" then
472+
for _, socket in ipairs(child) do
473+
if socket.elem == "Socket" then
474+
local oldItemId = tonumber(socket.attrib.itemId)
475+
if oldItemId and itemIdMap[oldItemId] then
476+
socket.attrib.itemId = tostring(itemIdMap[oldItemId])
477+
end
478+
end
479+
end
480+
end
481+
end
482+
end
483+
464484
local newSpec = new("PassiveSpec", self.build, node.attrib.treeVersion or defaultTreeVersion)
465485
newSpec:Load(node, dbFileName)
466486
t_insert(self.specList, newSpec)
467487
end
468488
end
469489
end
470-
if not self.specList[1] then
471-
self.specList[1] = new("PassiveSpec", self.build, latestTreeVersion)
490+
if not appendSpecs then
491+
if not self.specList[1] then
492+
self.specList[1] = new("PassiveSpec", self.build, latestTreeVersion)
493+
end
494+
self:SetActiveSpec(tonumber(xml.attrib.activeSpec) or 1)
472495
end
473-
self:SetActiveSpec(tonumber(xml.attrib.activeSpec) or 1)
474496
end
475497

476498
function TreeTabClass:PostLoad()

src/Modules/Build.lua

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,17 +1775,26 @@ do
17751775
end
17761776
end
17771777

1778-
function buildMode:LoadDB(xmlText, fileName)
1778+
function buildMode:ParseXML(xmlText, fileName)
17791779
-- Parse the XML
17801780
local dbXML, errMsg = common.xml.ParseXML(xmlText)
17811781
if not dbXML then
17821782
launch:ShowErrMsg("^1Error loading '%s': %s", fileName, errMsg)
1783-
return true
1783+
return nil
17841784
elseif #dbXML == 0 then
17851785
main:OpenMessagePopup("Error", "Build file is empty, or error parsing xml.\n\n"..fileName)
1786-
return true
1786+
return nil
17871787
elseif dbXML[1].elem ~= "PathOfBuilding" then
17881788
launch:ShowErrMsg("^1Error parsing '%s': 'PathOfBuilding' root element missing", fileName)
1789+
return nil
1790+
end
1791+
return dbXML
1792+
end
1793+
1794+
function buildMode:LoadDB(xmlText, fileName)
1795+
-- Parse the XML
1796+
local dbXML = self:ParseXML(xmlText, fileName)
1797+
if not dbXML then
17891798
return true
17901799
end
17911800

@@ -1830,6 +1839,83 @@ function buildMode:LoadDBFile()
18301839
return self:LoadDB(xmlText, self.dbFileName)
18311840
end
18321841

1842+
function buildMode:ImportLoadouts(xmlText)
1843+
-- Parse the XML
1844+
local dbXML = self:ParseXML(xmlText, self.dbFileName)
1845+
if not dbXML then
1846+
return true
1847+
end
1848+
1849+
-- Extract relevant sections from imported XML
1850+
local itemsSection = nil
1851+
local skillsSection = nil
1852+
local treeSection = nil
1853+
local itemSetCount = 0
1854+
local skillSetCount = 0
1855+
local treeSpecCount = 0
1856+
1857+
for _, node in ipairs(dbXML[1]) do
1858+
if type(node) == "table" then
1859+
if node.elem == "Items" then
1860+
itemsSection = node
1861+
for _, n in ipairs(itemsSection) do
1862+
if n.elem == "ItemSet" then
1863+
itemSetCount = itemSetCount + 1
1864+
end
1865+
end
1866+
elseif node.elem == "Skills" then
1867+
skillsSection = node
1868+
for _, n in ipairs(skillsSection) do
1869+
if n.elem == "SkillSet" then
1870+
skillSetCount = skillSetCount + 1
1871+
end
1872+
end
1873+
elseif node.elem == "Tree" then
1874+
treeSection = node
1875+
for _, n in ipairs(treeSection) do
1876+
if n.elem == "Spec" then
1877+
treeSpecCount = treeSpecCount + 1
1878+
end
1879+
end
1880+
end
1881+
end
1882+
end
1883+
1884+
if itemSetCount == 0 and skillSetCount == 0 and treeSpecCount == 0 then
1885+
main:OpenMessagePopup("Import Loadouts", "No loadouts (item sets, skill sets, or tree specs) found in the imported build.")
1886+
return
1887+
end
1888+
1889+
local itemIdMap = { }
1890+
if itemsSection then
1891+
itemIdMap = self.itemsTab:Load(itemsSection, self.dbFileName, true) or { }
1892+
end
1893+
if skillsSection then
1894+
self.skillsTab:Load(skillsSection, self.dbFileName, true)
1895+
end
1896+
if treeSection then
1897+
self.treeTab:Load(treeSection, self.dbFileName, true, itemIdMap)
1898+
self.treeTab:PostLoad()
1899+
end
1900+
1901+
-- Mark build as modified
1902+
self.modFlag = true
1903+
self.buildFlag = true
1904+
1905+
-- Show success message
1906+
local message = "Successfully imported:\n"
1907+
if itemSetCount > 0 then
1908+
message = message .. "- " .. itemSetCount .. " item set(s)\n"
1909+
end
1910+
if skillSetCount > 0 then
1911+
message = message .. "- " .. skillSetCount .. " skill set(s)\n"
1912+
end
1913+
if treeSpecCount > 0 then
1914+
message = message .. "- " .. treeSpecCount .. " tree spec(s)\n"
1915+
end
1916+
main:OpenMessagePopup("Import Complete", message)
1917+
end
1918+
18331919
function buildMode:SaveDB(fileName)
18341920
local dbXML = { elem = "PathOfBuilding" }
18351921

0 commit comments

Comments
 (0)