Files
PathOfBuilding/Classes/ImportTab.lua
Openarl 3d571703f5 Release 1.4.133
- Various skill fixes
- Fixed Synthesised item importing
2019-03-12 00:50:04 +13:00

822 lines
34 KiB
Lua

-- Path of Building
--
-- Module: Import Tab
-- Import/Export tab for the current build.
--
local ipairs = ipairs
local t_insert = table.insert
local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(self, build)
self.ControlHost()
self.Control()
self.build = build
self.charImportMode = build.targetVersion == liveTargetVersion and "GETACCOUNTNAME" or "VERSIONWARNING"
self.charImportStatus = "Idle"
self.controls.sectionCharImport = new("SectionControl", {"TOPLEFT",self,"TOPLEFT"}, 10, 18, 600, 250, "Character Import")
self.controls.charImportVersionWarning = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 20, 0, 16, colorCodes.WARNING..[[
Warning:^7 Characters may not import into this build correctly,
as the build's game version is different from the live game version.
Some passives may be deallocated, and some gems may not be recognised.
If possible, change the game version in the Configuration tab before importing.]])
self.controls.charImportVersionWarning.shown = function()
return self.charImportMode == "VERSIONWARNING"
end
self.controls.charImportVersionWarningGo = new("ButtonControl", {"TOPLEFT",self.controls.charImportVersionWarning,"TOPLEFT"}, 0, 70, 80, 20, "Continue", function()
self.charImportMode = "GETACCOUNTNAME"
end)
self.controls.charImportStatusLabel = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 14, 200, 16, function()
return "^7Character import status: "..self.charImportStatus
end)
self.controls.charImportStatusLabel.shown = function()
return self.charImportMode ~= "VERSIONWARNING"
end
-- Stage: input account name
self.controls.accountNameHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7To start importing a character, enter the character's account name:")
self.controls.accountNameHeader.shown = function()
return self.charImportMode == "GETACCOUNTNAME"
end
self.controls.accountName = new("EditControl", {"TOPLEFT",self.controls.accountNameHeader,"BOTTOMLEFT"}, 0, 4, 200, 20, main.lastAccountName or "", nil, "%c")
self.controls.accountName.pasteFilter = function(text)
return text:gsub("[\128-\255]",function(c)
return codePointToUTF8(c:byte(1)):gsub(".",function(c)
return string.format("%%%X", c:byte(1))
end)
end)
end
self.controls.accountNameGo = new("ButtonControl", {"LEFT",self.controls.accountName,"RIGHT"}, 8, 0, 60, 20, "Start", function()
self.controls.sessionInput.buf = ""
self:DownloadCharacterList()
end)
self.controls.accountNameGo.enabled = function()
return self.controls.accountName.buf:match("%S")
end
self.controls.accountNameUnicode = new("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, 0, 16, 0, 14, "^7Note: if the account name contains non-ASCII characters then it must be URL encoded first.")
self.controls.accountNameURLEncoder = new("ButtonControl", {"TOPLEFT",self.controls.accountNameUnicode,"BOTTOMLEFT"}, 0, 4, 170, 18, "^x4040FFhttps://www.urlencoder.org/", function()
OpenURL("https://www.urlencoder.org/")
end)
-- Stage: input POESESSID
self.controls.sessionHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 14)
self.controls.sessionHeader.label = function()
return [[
^7The list of characters on ']]..self.controls.accountName.buf..[[' couldn't be retrieved. This may be because:
1. The account name is wrong, or
2. The account's privacy settings hide the characters tab (this is the default setting).
If this is your account, you can either:
1. Change your privacy settings to show you characters tab and then retry, or
2. Enter a valid POESESSID below.
You can get this from your web browser's cookies while logged into the Path of Exile website.
]]
end
self.controls.sessionHeader.shown = function()
return self.charImportMode == "GETSESSIONID"
end
self.controls.sessionRetry = new("ButtonControl", {"TOPLEFT",self.controls.sessionHeader,"TOPLEFT"}, 0, 108, 60, 20, "Retry", function()
self.controls.sessionInput.buf = ""
self:DownloadCharacterList()
end)
self.controls.sessionCancel = new("ButtonControl", {"LEFT",self.controls.sessionRetry,"RIGHT"}, 8, 0, 60, 20, "Cancel", function()
self.charImportMode = "GETACCOUNTNAME"
self.charImportStatus = "Idle"
end)
self.controls.sessionInput = new("EditControl", {"TOPLEFT",self.controls.sessionRetry,"BOTTOMLEFT"}, 0, 8, 350, 20, "", "POESESSID", "%X", 32)
self.controls.sessionGo = new("ButtonControl", {"LEFT",self.controls.sessionInput,"RIGHT"}, 8, 0, 60, 20, "Go", function()
self:DownloadCharacterList()
end)
self.controls.sessionGo.enabled = function()
return #self.controls.sessionInput.buf == 32
end
-- Stage: select character and import data
self.controls.charSelectHeader = new("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 16, "^7Choose character to import data from:")
self.controls.charSelectHeader.shown = function()
return self.charImportMode == "SELECTCHAR" or self.charImportMode == "IMPORTING"
end
self.controls.charSelectLeagueLabel = new("LabelControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, 0, 6, 0, 14, "^7League:")
self.controls.charSelectLeague = new("DropDownControl", {"LEFT",self.controls.charSelectLeagueLabel,"RIGHT"}, 4, 0, 150, 18, nil, function(index, value)
self:BuildCharacterList(value.league)
end)
self.controls.charSelect = new("DropDownControl", {"TOPLEFT",self.controls.charSelectHeader,"BOTTOMLEFT"}, 0, 24, 400, 18)
self.controls.charSelect.enabled = function()
return self.charImportMode == "SELECTCHAR"
end
self.controls.charImportHeader = new("LabelControl", {"TOPLEFT",self.controls.charSelect,"BOTTOMLEFT"}, 0, 16, 200, 16, "Import:")
self.controls.charImportTree = new("ButtonControl", {"LEFT",self.controls.charImportHeader, "RIGHT"}, 8, 0, 170, 20, "Passive Tree and Jewels", function()
if self.build.spec:CountAllocNodes() > 0 then
main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", "Import", function()
self:DownloadPassiveTree()
end)
else
self:DownloadPassiveTree()
end
end)
self.controls.charImportTree.enabled = function()
return self.charImportMode == "SELECTCHAR"
end
self.controls.charImportTreeClearJewels = new("CheckBoxControl", {"LEFT",self.controls.charImportTree,"RIGHT"}, 90, 0, 18, "Delete jewels:")
self.controls.charImportTreeClearJewels.tooltipText = "Delete all existing jewels when importing."
self.controls.charImportItems = new("ButtonControl", {"LEFT",self.controls.charImportTree, "LEFT"}, 0, 36, 110, 20, "Items and Skills", function()
self:DownloadItems()
end)
self.controls.charImportItems.enabled = function()
return self.charImportMode == "SELECTCHAR"
end
self.controls.charImportItemsClearSkills = new("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, 85, 0, 18, "Delete skills:")
self.controls.charImportItemsClearSkills.tooltipText = "Delete all existing skills when importing."
self.controls.charImportItemsClearItems = new("CheckBoxControl", {"LEFT",self.controls.charImportItems,"RIGHT"}, 220, 0, 18, "Delete equipment:")
self.controls.charImportItemsClearItems.tooltipText = "Delete all equipped items when importing."
self.controls.charBanditNote = new("LabelControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, 0, 50, 200, 14, "^7Tip: After you finish importing a character, make sure you update the bandit choices,\nas these cannot be imported.")
self.controls.charDone = new("ButtonControl", {"TOPLEFT",self.controls.charImportHeader,"BOTTOMLEFT"}, 0, 90, 60, 20, "Done", function()
self.charImportMode = "GETACCOUNTNAME"
self.charImportStatus = "Idle"
end)
-- Build import/export
self.controls.sectionBuild = new("SectionControl", {"TOPLEFT",self.controls.sectionCharImport,"BOTTOMLEFT"}, 0, 18, 600, 200, "Build Sharing")
self.controls.generateCodeLabel = new("LabelControl", {"TOPLEFT",self.controls.sectionBuild,"TOPLEFT"}, 6, 14, 0, 16, "^7Generate a code to share this build with other Path of Building users:")
self.controls.generateCode = new("ButtonControl", {"LEFT",self.controls.generateCodeLabel,"RIGHT"}, 4, 0, 80, 20, "Generate", function()
self.controls.generateCodeOut:SetText(common.base64.encode(Deflate(self.build:SaveDB("code"))):gsub("+","-"):gsub("/","_"))
end)
self.controls.generateCodeOut = new("EditControl", {"TOPLEFT",self.controls.generateCodeLabel,"BOTTOMLEFT"}, 0, 8, 250, 20, "", "Code", "%Z")
self.controls.generateCodeOut.enabled = function()
return #self.controls.generateCodeOut.buf > 0
end
self.controls.generateCodeCopy = new("ButtonControl", {"LEFT",self.controls.generateCodeOut,"RIGHT"}, 8, 0, 60, 20, "Copy", function()
Copy(self.controls.generateCodeOut.buf)
self.controls.generateCodeOut:SetText("")
end)
self.controls.generateCodeCopy.enabled = function()
return #self.controls.generateCodeOut.buf > 0
end
self.controls.generateCodePastebin = new("ButtonControl", {"LEFT",self.controls.generateCodeCopy,"RIGHT"}, 8, 0, 140, 20, "Share with Pastebin", function()
local id = LaunchSubScript([[
local code, proxyURL = ...
local curl = require("lcurl.safe")
local page = ""
local easy = curl.easy()
easy:setopt_url("https://pastebin.com/api/api_post.php")
easy:setopt(curl.OPT_POST, true)
easy:setopt(curl.OPT_POSTFIELDS, "api_dev_key=c4757f22e50e65e21c53892fd8e0a9ff&api_paste_private=1&api_option=paste&api_paste_code="..code)
easy:setopt(curl.OPT_ACCEPT_ENCODING, "")
if proxyURL then
easy:setopt(curl.OPT_PROXY, proxyURL)
end
easy:setopt_writefunction(function(data)
page = page..data
return true
end)
easy:perform()
easy:close()
if page:match("pastebin.com") then
return page
else
return nil, page
end
]], "", "", self.controls.generateCodeOut.buf, launch.proxyURL)
if id then
self.controls.generateCodeOut:SetText("")
self.controls.generateCodePastebin.label = "Creating paste..."
launch:RegisterSubScript(id, function(pasteLink, errMsg)
self.controls.generateCodePastebin.label = "Share with Pastebin"
if errMsg then
main:OpenMessagePopup("Pastebin.com", "Error creating paste:\n"..errMsg)
else
self.controls.generateCodeOut:SetText(pasteLink)
end
end)
end
end)
self.controls.generateCodePastebin.enabled = function()
return #self.controls.generateCodeOut.buf > 0 and not self.controls.generateCodeOut.buf:match("pastebin%.com")
end
self.controls.generateCodeNote = new("LabelControl", {"TOPLEFT",self.controls.generateCodeOut,"BOTTOMLEFT"}, 0, 4, 0, 14, "^7Note: this code can be very long; you can use 'Share with Pastebin' to shrink it.")
self.controls.importCodeHeader = new("LabelControl", {"TOPLEFT",self.controls.generateCodeNote,"BOTTOMLEFT"}, 0, 26, 0, 16, "^7To import a build, enter the code here:")
self.controls.importCodeIn = new("EditControl", {"TOPLEFT",self.controls.importCodeHeader,"BOTTOMLEFT"}, 0, 4, 250, 20, "", nil, "^%w_%-=", nil, function(buf)
if #buf == 0 then
self.importCodeState = nil
return
end
self.importCodeState = "INVALID"
local xmlText = Inflate(common.base64.decode(buf:gsub("-","+"):gsub("_","/")))
if not xmlText then
return
end
if launch.devMode and IsKeyDown("SHIFT") then
Copy(xmlText)
end
self.importCodeState = "VALID"
self.importCodeXML = xmlText
if not self.build.dbFileName then
self.controls.importCodeMode.selIndex = 2
end
end)
self.controls.importCodeState = new("LabelControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, 4, 0, 0, 16)
self.controls.importCodeState.label = function()
return (self.importCodeState == "VALID" and colorCodes.POSITIVE.."Code is valid") or (self.importCodeState == "INVALID" and colorCodes.NEGATIVE.."Invalid code") or ""
end
self.controls.importCodePastebin = new("ButtonControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, 90, 0, 160, 20, "Import from Pastebin...", function()
self:OpenPastebinImportPopup()
end)
self.controls.importCodeMode = new("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, 0, 4, 160, 20, { "Import to this build", "Import to a new build" })
self.controls.importCodeMode.enabled = function()
return self.importCodeState == "VALID" and self.build.dbFileName
end
self.controls.importCodeGo = new("ButtonControl", {"TOPLEFT",self.controls.importCodeMode,"BOTTOMLEFT"}, 0, 8, 60, 20, "Import", function()
if self.controls.importCodeMode.selIndex == 1 then
main:OpenConfirmPopup("Build Import", colorCodes.WARNING.."Warning:^7 Importing to the current build will erase ALL existing data for this build.", "Import", function()
self.build:Shutdown()
self.build:Init(self.build.dbFileName, self.build.buildName, self.importCodeXML)
self.build.viewMode = "TREE"
end)
else
self.build:Shutdown()
self.build:Init(false, "Imported build", self.importCodeXML)
self.build.viewMode = "TREE"
end
end)
self.controls.importCodeGo.enabled = function()
return self.importCodeState == "VALID"
end
end)
function ImportTabClass:Load(xml, fileName)
self.lastAccountHash = xml.attrib.lastAccountHash
if self.lastAccountHash then
for accountName in pairs(main.gameAccounts) do
if common.sha1(accountName) == self.lastAccountHash then
self.controls.accountName:SetText(accountName)
end
end
end
self.lastCharacterHash = xml.attrib.lastCharacterHash
end
function ImportTabClass:Save(xml)
xml.attrib = {
lastAccountHash = self.lastAccountHash,
lastCharacterHash = self.lastCharacterHash,
}
end
function ImportTabClass:Draw(viewPort, inputEvents)
self.x = viewPort.x
self.y = viewPort.y
self.width = viewPort.width
self.height = viewPort.height
self:ProcessControlsInput(inputEvents, viewPort)
main:DrawBackground(viewPort)
self:DrawControls(viewPort)
end
function ImportTabClass:DownloadCharacterList()
self.charImportMode = "DOWNLOADCHARLIST"
self.charImportStatus = "Retrieving character list..."
local accountName = self.controls.accountName.buf
local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
launch:DownloadPage("https://www.pathofexile.com/character-window/get-characters?accountName="..accountName, function(page, errMsg)
if errMsg == "Response code: 403" then
self.charImportStatus = colorCodes.NEGATIVE.."Account profile is private."
self.charImportMode = "GETSESSIONID"
return
elseif errMsg == "Response code: 404" then
self.charImportStatus = colorCodes.NEGATIVE.."Account name is incorrect."
self.charImportMode = "GETACCOUNTNAME"
return
elseif errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error retrieving character list, try again ("..errMsg:gsub("\n"," ")..")"
self.charImportMode = "GETACCOUNTNAME"
return
end
local charList, errMsg = self:ProcessJSON(page)
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error processing character list, try again later"
self.charImportMode = "GETACCOUNTNAME"
return
end
--ConPrintTable(charList)
if #charList == 0 then
self.charImportStatus = colorCodes.NEGATIVE.."The account has no characters to import."
self.charImportMode = "GETACCOUNTNAME"
return
end
-- GGG's character API has an issue where for /get-characters the account name is not case-sensitive, but for /get-passive-skills and /get-items it is.
-- This workaround grabs the profile page and extracts the correct account name from one of the URLs.
launch:DownloadPage("https://www.pathofexile.com/account/view-profile/"..accountName, function(page, errMsg)
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error retrieving character list, try again ("..errMsg:gsub("\n"," ")..")"
self.charImportMode = "GETACCOUNTNAME"
return
end
local realAccountName = page:match("/account/view%-profile/([^/]+)/characters"):gsub(".", function(c) if c:byte(1) > 127 then return string.format("%%%2X",c:byte(1)) else return c end end)
if not realAccountName then
self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character list."
self.charImportMode = "GETSESSIONID"
return
end
self.controls.accountName:SetText(realAccountName)
accountName = realAccountName
self.charImportStatus = "Character list successfully retrieved."
self.charImportMode = "SELECTCHAR"
self.lastAccountHash = common.sha1(accountName)
main.lastAccountName = accountName
main.gameAccounts[accountName] = main.gameAccounts[accountName] or { }
main.gameAccounts[accountName].sessionID = sessionID
local leagueList = { }
for i, char in ipairs(charList) do
if not isValueInArray(leagueList, char.league) then
t_insert(leagueList, char.league)
end
end
table.sort(leagueList)
wipeTable(self.controls.charSelectLeague.list)
t_insert(self.controls.charSelectLeague.list, {
label = "All",
})
for _, league in ipairs(leagueList) do
t_insert(self.controls.charSelectLeague.list, {
label = league,
league = league,
})
end
self.lastCharList = charList
self:BuildCharacterList()
end, sessionID and "POESESSID="..sessionID)
end, sessionID and "POESESSID="..sessionID)
end
function ImportTabClass:BuildCharacterList(league)
wipeTable(self.controls.charSelect.list)
for i, char in ipairs(self.lastCharList) do
if not league or char.league == league then
t_insert(self.controls.charSelect.list, {
label = string.format("%s: Level %d %s in %s", char.name or "?", char.level or 0, char.class or "?", char.league or "?"),
char = char,
})
end
end
table.sort(self.controls.charSelect.list, function(a,b)
return a.char.name:lower() < b.char.name:lower()
end)
self.controls.charSelect.selIndex = 1
if self.lastCharacterHash then
for i, char in ipairs(self.controls.charSelect.list) do
if common.sha1(char.char.name) == self.lastCharacterHash then
self.controls.charSelect.selIndex = i
break
end
end
end
end
function ImportTabClass:DownloadPassiveTree()
self.charImportMode = "IMPORTING"
self.charImportStatus = "Retrieving character passive tree..."
local accountName = self.controls.accountName.buf
local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
local charSelect = self.controls.charSelect
local charData = charSelect.list[charSelect.selIndex].char
launch:DownloadPage("https://www.pathofexile.com/character-window/get-passive-skills?accountName="..accountName.."&character="..charData.name, function(page, errMsg)
self.charImportMode = "SELECTCHAR"
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")"
return
elseif page == "false" then
self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again."
return
end
self.lastCharacterHash = common.sha1(charData.name)
self:ImportPassiveTreeAndJewels(page, charData)
end, sessionID and "POESESSID="..sessionID)
end
function ImportTabClass:DownloadItems()
self.charImportMode = "IMPORTING"
self.charImportStatus = "Retrieving character items..."
local accountName = self.controls.accountName.buf
local sessionID = #self.controls.sessionInput.buf == 32 and self.controls.sessionInput.buf or (main.gameAccounts[accountName] and main.gameAccounts[accountName].sessionID)
local charSelect = self.controls.charSelect
local charData = charSelect.list[charSelect.selIndex].char
launch:DownloadPage("https://www.pathofexile.com/character-window/get-items?accountName="..accountName.."&character="..charData.name, function(page, errMsg)
self.charImportMode = "SELECTCHAR"
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error importing character data, try again ("..errMsg:gsub("\n"," ")..")"
return
elseif page == "false" then
self.charImportStatus = colorCodes.NEGATIVE.."Failed to retrieve character data, try again."
return
end
self.lastCharacterHash = common.sha1(charData.name)
self:ImportItemsAndSkills(page)
end, sessionID and "POESESSID="..sessionID)
end
function ImportTabClass:ImportPassiveTreeAndJewels(json, charData)
--local out = io.open("get-passive-skills.json", "w")
--out:write(json)
--out:close()
local charPassiveData, errMsg = self:ProcessJSON(json)
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later."
return
end
self.charImportStatus = colorCodes.POSITIVE.."Passive tree and jewels successfully imported."
--ConPrintTable(charPassiveData)
if self.controls.charImportTreeClearJewels.state then
for _, slot in pairs(self.build.itemsTab.slots) do
if slot.selItemId ~= 0 and slot.nodeId then
self.build.itemsTab:DeleteItem(self.build.itemsTab.items[slot.selItemId])
end
end
end
local sockets = { }
for i, slot in pairs(charPassiveData.jewel_slots) do
sockets[i] = tonumber(type(slot) == "number" and slot or slot.passiveSkill.hash)
end
for _, itemData in pairs(charPassiveData.items) do
self:ImportItem(itemData, sockets)
end
self.build.itemsTab:PopulateSlots()
self.build.itemsTab:AddUndoState()
self.build.spec:ImportFromNodeList(charData.classId, charData.ascendancyClass, charPassiveData.hashes)
self.build.spec:AddUndoState()
self.build.characterLevel = charData.level
self.build.controls.characterLevel:SetText(charData.level)
self.build.buildFlag = true
end
function ImportTabClass:ImportItemsAndSkills(json)
--local out = io.open("get-items.json", "w")
--out:write(json)
--out:close()
local charItemData, errMsg = self:ProcessJSON(json)
if errMsg then
self.charImportStatus = colorCodes.NEGATIVE.."Error processing character data, try again later."
return
end
if self.controls.charImportItemsClearItems.state then
for _, slot in pairs(self.build.itemsTab.slots) do
if slot.selItemId ~= 0 and not slot.nodeId then
self.build.itemsTab:DeleteItem(self.build.itemsTab.items[slot.selItemId])
end
end
end
local skillOrder
if self.controls.charImportItemsClearSkills.state then
skillOrder = { }
for _, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do
for _, gem in ipairs(socketGroup.gemList) do
if gem.grantedEffect and not gem.grantedEffect.support then
t_insert(skillOrder, gem.grantedEffect.name)
end
end
end
wipeTable(self.build.skillsTab.socketGroupList)
end
self.charImportStatus = colorCodes.POSITIVE.."Items and skills successfully imported."
--ConPrintTable(charItemData)
for _, itemData in pairs(charItemData.items) do
self:ImportItem(itemData)
end
if skillOrder then
local groupOrder = { }
for index, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do
groupOrder[socketGroup] = index
end
table.sort(self.build.skillsTab.socketGroupList, function(a, b)
local orderA
for _, gem in ipairs(a.gemList) do
if gem.grantedEffect and not gem.grantedEffect.support then
local i = isValueInArray(skillOrder, gem.grantedEffect.name)
if i and (not orderA or i < orderA) then
orderA = i
end
end
end
local orderB
for _, gem in ipairs(b.gemList) do
if gem.grantedEffect and not gem.grantedEffect.support then
local i = isValueInArray(skillOrder, gem.grantedEffect.name)
if i and (not orderB or i < orderB) then
orderB = i
end
end
end
if orderA and orderB then
if orderA ~= orderB then
return orderA < orderB
else
return groupOrder[a] < groupOrder[b]
end
elseif not orderA and not orderB then
return groupOrder[a] < groupOrder[b]
else
return orderA
end
end)
end
self.build.itemsTab:PopulateSlots()
self.build.itemsTab:AddUndoState()
self.build.skillsTab:AddUndoState()
self.build.characterLevel = charItemData.character.level
self.build.controls.characterLevel:SetText(charItemData.character.level)
self.build.buildFlag = true
return charItemData.character -- For the wrapper
end
local rarityMap = { [0] = "NORMAL", "MAGIC", "RARE", "UNIQUE", [9] = "RELIC" }
local slotMap = { ["Weapon"] = "Weapon 1", ["Offhand"] = "Weapon 2", ["Weapon2"] = "Weapon 1 Swap", ["Offhand2"] = "Weapon 2 Swap", ["Helm"] = "Helmet", ["BodyArmour"] = "Body Armour", ["Gloves"] = "Gloves", ["Boots"] = "Boots", ["Amulet"] = "Amulet", ["Ring"] = "Ring 1", ["Ring2"] = "Ring 2", ["Belt"] = "Belt" }
function ImportTabClass:ImportItem(itemData, sockets, slotName)
if not slotName then
if itemData.inventoryId == "PassiveJewels" and sockets then
slotName = "Jewel "..sockets[itemData.x + 1]
elseif itemData.inventoryId == "Flask" then
slotName = "Flask "..(itemData.x + 1)
else
slotName = slotMap[itemData.inventoryId]
end
end
if not slotName then
-- Ignore any items that won't go into known slots
return
end
local item = new("Item", self.build.targetVersion)
-- Determine rarity, display name and base type of the item
item.rarity = rarityMap[itemData.frameType]
if #itemData.name > 0 then
item.title = itemLib.sanitiseItemText(itemData.name)
item.baseName = itemLib.sanitiseItemText(itemData.typeLine):gsub("Synthesised ","")
item.name = item.title .. ", " .. item.baseName
if item.baseName == "Two-Toned Boots" then
-- Hack for Two-Toned Boots
item.baseName = "Two-Toned Boots (Armour/Energy Shield)"
end
item.base = self.build.data.itemBases[item.baseName]
if item.base then
item.type = item.base.type
else
ConPrintf("Unrecognised base in imported item: %s", item.baseName)
end
else
item.name = itemLib.sanitiseItemText(itemData.typeLine)
for baseName, baseData in pairs(self.build.data.itemBases) do
local s, e = item.name:find(baseName, 1, true)
if s then
item.baseName = baseName
item.namePrefix = item.name:sub(1, s - 1)
item.nameSuffix = item.name:sub(e + 1)
item.type = baseData.type
break
end
end
if not item.baseName then
local s, e = item.name:find("Two-Toned Boots", 1, true)
if s then
-- Hack for Two-Toned Boots
item.baseName = "Two-Toned Boots (Armour/Energy Shield)"
item.namePrefix = item.name:sub(1, s - 1)
item.nameSuffix = item.name:sub(e + 1)
item.type = "Boots"
end
end
item.base = self.build.data.itemBases[item.baseName]
end
if not item.base or not item.rarity then
return
end
-- Import item data
item.uniqueID = itemData.id
item.shaper = itemData.shaper
item.elder = itemData.elder
if itemData.ilvl > 0 then
item.itemLevel = itemData.ilvl
end
if item.base.weapon or item.base.armour or item.base.flask then
item.quality = 0
end
if itemData.properties then
for _, property in pairs(itemData.properties) do
if property.name == "Quality" then
item.quality = tonumber(property.values[1][1]:match("%d+"))
elseif property.name == "Radius" then
for index, data in pairs(self.build.data.jewelRadius) do
if property.values[1][1] == data.label then
item.jewelRadiusIndex = index
break
end
end
elseif property.name == "Limited to" then
item.limit = tonumber(property.values[1][1])
elseif property.name == "Evasion Rating" then
if item.baseName == "Two-Toned Boots (Armour/Energy Shield)" then
-- Another hack for Two-Toned Boots
item.baseName = "Two-Toned Boots (Armour/Evasion)"
item.base = self.build.data.itemBases[item.baseName]
end
elseif property.name == "Energy Shield" then
if item.baseName == "Two-Toned Boots (Armour/Evasion)" then
-- Yet another hack for Two-Toned Boots
item.baseName = "Two-Toned Boots (Evasion/Energy Shield)"
item.base = self.build.data.itemBases[item.baseName]
end
end
end
end
if itemData.corrupted then
item.corrupted = true
end
if itemData.sockets and itemData.sockets[1] then
item.sockets = { }
for i, socket in pairs(itemData.sockets) do
item.sockets[i] = { group = socket.group, color = socket.sColour }
end
end
if itemData.socketedItems then
self:ImportSocketedItems(item, itemData.socketedItems, slotName)
end
if itemData.requirements and (not itemData.socketedItems or not itemData.socketedItems[1]) then
-- Requirements cannot be trusted if there are socketed gems, as they may override the item's natural requirements
item.requirements = { }
for _, req in ipairs(itemData.requirements) do
if req.name == "Level" then
item.requirements.level = req.values[1][1]
end
end
end
item.modLines = { }
item.implicitLines = 0
if itemData.implicitMods then
item.implicitLines = item.implicitLines + #itemData.implicitMods
for _, line in ipairs(itemData.implicitMods) do
line = line:gsub("\n"," ")
local modList, extra = modLib.parseMod[self.build.targetVersion](line)
t_insert(item.modLines, { line = line, extra = extra, mods = modList or { } })
end
end
if itemData.enchantMods then
item.implicitLines = item.implicitLines + #itemData.enchantMods
for _, line in ipairs(itemData.enchantMods) do
line = line:gsub("\n"," ")
local modList, extra = modLib.parseMod[self.build.targetVersion](line)
t_insert(item.modLines, { line = line, extra = extra, mods = modList or { }, crafted = true })
end
end
if itemData.explicitMods then
for _, line in ipairs(itemData.explicitMods) do
for line in line:gmatch("[^\n]+") do
local modList, extra = modLib.parseMod[self.build.targetVersion](line)
t_insert(item.modLines, { line = line, extra = extra, mods = modList or { } })
end
end
end
if itemData.craftedMods then
for _, line in ipairs(itemData.craftedMods) do
for line in line:gmatch("[^\n]+") do
local modList, extra = modLib.parseMod[self.build.targetVersion](line)
t_insert(item.modLines, { line = line, extra = extra, mods = modList or { }, crafted = true })
end
end
end
-- Add and equip the new item
item:BuildAndParseRaw()
--ConPrintf("%s", item.raw)
if item.base then
local repIndex, repItem
for index, item in pairs(self.build.itemsTab.items) do
if item.uniqueID == itemData.id then
repIndex = index
repItem = item
break
end
end
if repIndex then
-- Item already exists in the build, overwrite it
item.id = repItem.id
self.build.itemsTab.items[item.id] = item
item:BuildModList()
else
self.build.itemsTab:AddItem(item, true)
end
self.build.itemsTab.slots[slotName]:SetSelItemId(item.id)
end
end
function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
-- Build socket group list
local itemSocketGroupList = { }
local abyssalSocketId = 1
for _, socketedItem in ipairs(socketedItems) do
if socketedItem.abyssJewel then
self:ImportItem(socketedItem, nil, slotName .. " Abyssal Socket "..abyssalSocketId)
abyssalSocketId = abyssalSocketId + 1
else
local gemInstance = { level = 20, quality = 0, enabled = true, enableGlobal1 = true }
gemInstance.nameSpec = socketedItem.typeLine:gsub(" Support","")
gemInstance.support = socketedItem.support
for _, property in pairs(socketedItem.properties) do
if property.name == "Level" then
gemInstance.level = tonumber(property.values[1][1]:match("%d+"))
elseif property.name == "Quality" then
gemInstance.quality = tonumber(property.values[1][1]:match("%d+"))
end
end
local groupID = item.sockets[socketedItem.socket + 1].group
if not itemSocketGroupList[groupID] then
itemSocketGroupList[groupID] = { label = "", enabled = true, gemList = { }, slot = slotName }
end
local socketGroup = itemSocketGroupList[groupID]
if not socketedItem.support and socketGroup.gemList[1] and socketGroup.gemList[1].support then
-- If the first gemInstance is a support gemInstance, put the first active gemInstance before it
t_insert(socketGroup.gemList, 1, gemInstance)
else
t_insert(socketGroup.gemList, gemInstance)
end
end
end
-- Import the socket groups
for _, itemSocketGroup in pairs(itemSocketGroupList) do
-- Check if this socket group matches an existing one
local repGroup
for index, socketGroup in pairs(self.build.skillsTab.socketGroupList) do
if #socketGroup.gemList == #itemSocketGroup.gemList and (not socketGroup.slot or socketGroup.slot == slotName) then
local match = true
for gemIndex, gem in pairs(socketGroup.gemList) do
if gem.nameSpec:lower() ~= itemSocketGroup.gemList[gemIndex].nameSpec:lower() then
match = false
break
end
end
if match then
repGroup = socketGroup
break
end
end
end
if repGroup then
-- Update the existing one
for gemIndex, gem in pairs(repGroup.gemList) do
local itemGem = itemSocketGroup.gemList[gemIndex]
gem.level = itemGem.level
gem.quality = itemGem.quality
end
else
t_insert(self.build.skillsTab.socketGroupList, itemSocketGroup)
end
self.build.skillsTab:ProcessSocketGroup(itemSocketGroup)
end
end
function ImportTabClass:OpenPastebinImportPopup()
local controls = { }
controls.editLabel = new("LabelControl", nil, 0, 20, 0, 16, "Enter Pastebin.com link:")
controls.edit = new("EditControl", nil, 0, 40, 250, 18, "", nil, "^%w%p%s", nil, function(buf)
controls.msg.label = ""
end)
controls.msg = new("LabelControl", nil, 0, 58, 0, 16, "")
controls.import = new("ButtonControl", nil, -45, 80, 80, 20, "Import", function()
controls.import.enabled = false
controls.msg.label = "Retrieving paste..."
controls.edit.buf = controls.edit.buf:gsub("^%s+", ""):gsub("%s+$", "") -- Quick Trim
launch:DownloadPage(controls.edit.buf:gsub("pastebin%.com/(%w+)%s*$","pastebin.com/raw/%1"), function(page, errMsg)
if errMsg then
controls.msg.label = "^1"..errMsg
controls.import.enabled = true
else
self.controls.importCodeIn:SetText(page, true)
main:ClosePopup()
end
end)
end)
controls.import.enabled = function()
return #controls.edit.buf > 0 and controls.edit.buf:match("pastebin%.com/%w+")
end
controls.cancel = new("ButtonControl", nil, 45, 80, 80, 20, "Cancel", function()
main:ClosePopup()
end)
main:OpenPopup(280, 110, "Import from Pastebin", controls, "import", "edit")
end
function ImportTabClass:ProcessJSON(json)
local func, errMsg = loadstring("return "..jsonToLua(json))
if errMsg then
return nil, errMsg
end
setfenv(func, { }) -- Sandbox the function just in case
local data = func()
if type(data) ~= "table" then
return nil, "Return type is not a table"
end
return data
end