Skip to content

Commit f29ceed

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 <slusnucky@gmail.com>
1 parent 1b581eb commit f29ceed

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
@@ -1214,4 +1216,4 @@ function ImportTabClass:SetPredefinedBuildName()
12141216
local charData = charSelect.list[charSelect.selIndex].char
12151217
local charName = charData.name
12161218
main.predefinedBuildName = accountName.." - "..charName
1217-
end
1219+
end

src/Classes/ItemsTab.lua

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

961-
function ItemsTabClass:Load(xml, dbFileName)
962-
self.activeItemSetId = 0
963-
self.itemSets = { }
964-
self.itemSetOrderList = { }
965-
self.tradeQuery.statSortSelectionList = { }
961+
function ItemsTabClass:Load(xml, dbFileName, appendItems)
962+
if not appendItems then
963+
self.activeItemSetId = 0
964+
self.itemSets = { }
965+
self.itemSetOrderList = { }
966+
self.tradeQuery.statSortSelectionList = { }
967+
end
968+
969+
local itemIdMap = { }
966970
for _, node in ipairs(xml) do
967971
if node.elem == "Item" then
968972
local item = new("Item", "")
969-
item.id = tonumber(node.attrib.id)
973+
local itemId = tonumber(node.attrib.id)
974+
if not appendItems then
975+
item.id = itemId
976+
end
977+
970978
item.variant = tonumber(node.attrib.variant)
971979
if node.attrib.variantAlt then
972980
item.hasAltVariant = true
@@ -1009,8 +1017,13 @@ function ItemsTabClass:Load(xml, dbFileName)
10091017
end
10101018
if item.base then
10111019
item:BuildModList()
1012-
self.items[item.id] = item
1013-
t_insert(self.itemOrderList, item.id)
1020+
if appendItems then
1021+
self:AddItem(item, true)
1022+
itemIdMap[itemId] = item.id
1023+
else
1024+
self.items[item.id] = item
1025+
t_insert(self.itemOrderList, item.id)
1026+
end
10141027
end
10151028
-- Below is OBE and left for legacy compatibility (all Slots are part of ItemSets now)
10161029
elseif node.elem == "Slot" then
@@ -1023,14 +1036,18 @@ function ItemsTabClass:Load(xml, dbFileName)
10231036
end
10241037
end
10251038
elseif node.elem == "ItemSet" then
1026-
local itemSet = self:NewItemSet(tonumber(node.attrib.id))
1039+
local itemSet = self:NewItemSet(not appendItems and tonumber(node.attrib.id) or nil)
10271040
itemSet.title = node.attrib.title
10281041
itemSet.useSecondWeaponSet = node.attrib.useSecondWeaponSet == "true"
10291042
for _, child in ipairs(node) do
10301043
if child.elem == "Slot" then
10311044
local slotName = child.attrib.name or ""
10321045
if itemSet[slotName] then
1033-
itemSet[slotName].selItemId = tonumber(child.attrib.itemId)
1046+
local itemId = tonumber(child.attrib.itemId)
1047+
if appendItems and itemIdMap[itemId] then
1048+
itemId = itemIdMap[itemId]
1049+
end
1050+
itemSet[slotName].selItemId = itemId
10341051
itemSet[slotName].active = child.attrib.active == "true"
10351052
itemSet[slotName].pbURL = child.attrib.itemPbURL or ""
10361053
end
@@ -1051,16 +1068,20 @@ function ItemsTabClass:Load(xml, dbFileName)
10511068
end
10521069
end
10531070
end
1054-
if not self.itemSetOrderList[1] then
1055-
self.activeItemSet = self:NewItemSet(1)
1056-
self.activeItemSet.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true"
1057-
self.itemSetOrderList[1] = 1
1058-
end
1059-
self:SetActiveItemSet(tonumber(xml.attrib.activeItemSet) or 1)
1060-
if xml.attrib.showStatDifferences then
1061-
self.showStatDifferences = xml.attrib.showStatDifferences == "true"
1071+
if not appendItems then
1072+
if not self.itemSetOrderList[1] then
1073+
self.activeItemSet = self:NewItemSet(1)
1074+
self.activeItemSet.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true"
1075+
self.itemSetOrderList[1] = 1
1076+
end
1077+
self:SetActiveItemSet(tonumber(xml.attrib.activeItemSet) or 1)
1078+
if xml.attrib.showStatDifferences then
1079+
self.showStatDifferences = xml.attrib.showStatDifferences == "true"
1080+
end
1081+
self:ResetUndo()
1082+
else
1083+
return itemIdMap
10621084
end
1063-
self:ResetUndo()
10641085
end
10651086

10661087
function ItemsTabClass:Save(xml)

src/Classes/SkillsTab.lua

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

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

414417
if node.elem == "SkillSet" then
415-
local skillSet = self:NewSkillSet(tonumber(node.attrib.id))
418+
local skillSet = self:NewSkillSet(not appendSkills and tonumber(node.attrib.id) or nil)
416419
skillSet.title = node.attrib.title
417420
t_insert(self.skillSetOrderList, skillSet.id)
418421
for _, subNode in ipairs(node) do
419422
self:LoadSkill(subNode, skillSet.id)
420423
end
421424
end
422425
end
423-
self:SetActiveSkillSet(tonumber(xml.attrib.activeSkillSet) or 1)
424-
self:ResetUndo()
426+
if not appendSkills then
427+
self:SetActiveSkillSet(tonumber(xml.attrib.activeSkillSet) or 1)
428+
self:ResetUndo()
429+
end
425430
end
426431

427432
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
@@ -1817,17 +1817,26 @@ do
18171817
end
18181818
end
18191819

1820-
function buildMode:LoadDB(xmlText, fileName)
1820+
function buildMode:ParseXML(xmlText, fileName)
18211821
-- Parse the XML
18221822
local dbXML, errMsg = common.xml.ParseXML(xmlText)
18231823
if not dbXML then
18241824
launch:ShowErrMsg("^1Error loading '%s': %s", fileName, errMsg)
1825-
return true
1825+
return nil
18261826
elseif #dbXML == 0 then
18271827
main:OpenMessagePopup("Error", "Build file is empty, or error parsing xml.\n\n"..fileName)
1828-
return true
1828+
return nil
18291829
elseif dbXML[1].elem ~= "PathOfBuilding" then
18301830
launch:ShowErrMsg("^1Error parsing '%s': 'PathOfBuilding' root element missing", fileName)
1831+
return nil
1832+
end
1833+
return dbXML
1834+
end
1835+
1836+
function buildMode:LoadDB(xmlText, fileName)
1837+
-- Parse the XML
1838+
local dbXML = self:ParseXML(xmlText, fileName)
1839+
if not dbXML then
18311840
return true
18321841
end
18331842

@@ -1872,6 +1881,83 @@ function buildMode:LoadDBFile()
18721881
return self:LoadDB(xmlText, self.dbFileName)
18731882
end
18741883

1884+
function buildMode:ImportLoadouts(xmlText)
1885+
-- Parse the XML
1886+
local dbXML = self:ParseXML(xmlText, self.dbFileName)
1887+
if not dbXML then
1888+
return true
1889+
end
1890+
1891+
-- Extract relevant sections from imported XML
1892+
local itemsSection = nil
1893+
local skillsSection = nil
1894+
local treeSection = nil
1895+
local itemSetCount = 0
1896+
local skillSetCount = 0
1897+
local treeSpecCount = 0
1898+
1899+
for _, node in ipairs(dbXML[1]) do
1900+
if type(node) == "table" then
1901+
if node.elem == "Items" then
1902+
itemsSection = node
1903+
for _, n in ipairs(itemsSection) do
1904+
if n.elem == "ItemSet" then
1905+
itemSetCount = itemSetCount + 1
1906+
end
1907+
end
1908+
elseif node.elem == "Skills" then
1909+
skillsSection = node
1910+
for _, n in ipairs(skillsSection) do
1911+
if n.elem == "SkillSet" then
1912+
skillSetCount = skillSetCount + 1
1913+
end
1914+
end
1915+
elseif node.elem == "Tree" then
1916+
treeSection = node
1917+
for _, n in ipairs(treeSection) do
1918+
if n.elem == "Spec" then
1919+
treeSpecCount = treeSpecCount + 1
1920+
end
1921+
end
1922+
end
1923+
end
1924+
end
1925+
1926+
if itemSetCount == 0 and skillSetCount == 0 and treeSpecCount == 0 then
1927+
main:OpenMessagePopup("Import Loadouts", "No loadouts (item sets, skill sets, or tree specs) found in the imported build.")
1928+
return
1929+
end
1930+
1931+
local itemIdMap = { }
1932+
if itemsSection then
1933+
itemIdMap = self.itemsTab:Load(itemsSection, self.dbFileName, true) or { }
1934+
end
1935+
if skillsSection then
1936+
self.skillsTab:Load(skillsSection, self.dbFileName, true)
1937+
end
1938+
if treeSection then
1939+
self.treeTab:Load(treeSection, self.dbFileName, true, itemIdMap)
1940+
self.treeTab:PostLoad()
1941+
end
1942+
1943+
-- Mark build as modified
1944+
self.modFlag = true
1945+
self.buildFlag = true
1946+
1947+
-- Show success message
1948+
local message = "Successfully imported:\n"
1949+
if itemSetCount > 0 then
1950+
message = message .. "- " .. itemSetCount .. " item set(s)\n"
1951+
end
1952+
if skillSetCount > 0 then
1953+
message = message .. "- " .. skillSetCount .. " skill set(s)\n"
1954+
end
1955+
if treeSpecCount > 0 then
1956+
message = message .. "- " .. treeSpecCount .. " tree spec(s)\n"
1957+
end
1958+
main:OpenMessagePopup("Import Complete", message)
1959+
end
1960+
18751961
function buildMode:SaveDB(fileName)
18761962
local dbXML = { elem = "PathOfBuilding" }
18771963

0 commit comments

Comments
 (0)