-- Path of Building
--
-- Module: Party Tab
-- Party tab for the current build.
--
local pairs = pairs
local ipairs = ipairs
local s_format = string.format
local t_insert = table.insert
local m_max = math.max
local PartyTabClass = newClass("PartyTab", "ControlHost", "Control", function(self, build)
self.ControlHost()
self.Control()
self.build = build
self.actor = { Aura = { }, Curse = { }, Warcry = { }, Link = { }, modDB = new("ModDB"), output = { } }
self.actor.modDB.actor = self.actor
self.enemyModList = new("ModList")
self.buffExports = { }
self.enableExportBuffs = false
self.lastContent = {
Aura = "",
Curse = "",
Warcry = "",
Link = "",
EnemyCond = "",
EnemyMods = "",
EnableExportBuffs = false,
showAdvancedTools = false,
}
local partyDestinations = { "All", "Party Member Stats", "Aura", "Curse", "Warcry Skills", "Link Skills", "EnemyConditions", "EnemyMods" }
local theme = {
stringHeight = 16,
buttonHeight = 20,
lineCounter = function(label)
local lineCount = 0
for i = 1, #label do
local c = label:sub(i, i)
if c == '\n' then lineCount = lineCount + 1 end
end
return lineCount * 16
end,
widthThreshold1 = 1350,
bufferHeightSmall = 106,
bufferHeightLeft = function()
-- 2 elements
return (self.height - 378 - ((self.width > 1350) and 0 or 24) - self.controls.importCodeHeader.y() - self.controls.editAurasLabel.y())
end,
-- 4 elements
bufferHeightRight = 434,
}
local notesDesc = [[^7To import a build it must be exported with "Export support" enabled in the import/export tab
Auras with the highest effect will take priority, your curses will take priority over a support's
All of these effects can be found in the Calcs tab]]
self.controls.notesDesc = new("LabelControl", {"TOPLEFT",self,"TOPLEFT"}, {8, 8, 150, theme.stringHeight}, notesDesc)
self.controls.notesDesc.width = function()
local width = self.width / 2 - 16
if width ~= self.controls.notesDesc.lastWidth then
self.controls.notesDesc.lastWidth = width
self.controls.notesDesc.label = table.concat(main:WrapString(notesDesc, theme.stringHeight, width - 50), "\n")
end
return width
end
self.controls.importCodeHeader = new("LabelControl", {"TOPLEFT",self.controls.notesDesc,"BOTTOMLEFT"}, {0, 32, 0, theme.stringHeight}, "^7Enter a build code/URL below:")
self.controls.importCodeHeader.y = function()
return theme.lineCounter(self.controls.notesDesc.label) + 4
end
local clearInputText = function()
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Party Member Stats" then
self.controls.editPartyMemberStats:SetText("")
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Aura" then
self.controls.simpleAuras.label = ""
self.controls.editAuras:SetText("")
wipeTable(self.actor["Aura"])
self.actor["Aura"] = {}
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Curse" then
self.controls.simpleCurses.label = ""
self.controls.editCurses:SetText("")
wipeTable(self.actor["Curse"])
self.actor["Curse"] = {}
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Warcry Skills" then
self.controls.simpleWarcries.label = ""
self.controls.editWarcries:SetText("")
wipeTable(self.actor["Warcry"])
self.actor["Warcry"] = {}
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Link Skills" then
self.controls.simpleLinks.label = ""
self.controls.editLinks:SetText("")
wipeTable(self.actor["Link"])
self.actor["Link"] = {}
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyConditions" then
self.controls.simpleEnemyCond.label = "^7---------------------------\n"
self.controls.enemyCond:SetText("")
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyMods" then
self.controls.simpleEnemyMods.label = "\n"
self.controls.enemyMods:SetText("")
end
end
local importCodeHandle = function (buf)
self.importCodeSite = nil
self.importCodeDetail = ""
self.importCodeXML = nil
self.importCodeValid = false
if #buf == 0 then
return
end
self.importCodeDetail = colorCodes.NEGATIVE.."Invalid input"
local urlText = buf:gsub("^[%s?]+", ""):gsub("[%s?]+$", "") -- Quick Trim
if urlText:match("youtube%.com/redirect%?") or urlText:match("google%.com/url%?") then
local nested_url = urlText:gsub(".*[?&]q=([^&]+).*", "%1")
urlText = UrlDecode(nested_url)
end
for j=1,#buildSites.websiteList do
if urlText:match(buildSites.websiteList[j].matchURL) then
self.controls.importCodeIn.text = urlText
self.importCodeValid = true
self.importCodeDetail = colorCodes.POSITIVE.."URL is valid ("..buildSites.websiteList[j].label..")"
self.importCodeSite = j
if buf ~= urlText then
self.controls.importCodeIn:SetText(urlText, false)
end
return
end
end
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.importCodeValid = true
self.importCodeDetail = colorCodes.POSITIVE.."Code is valid"
self.importCodeXML = xmlText
end
local finishImport = function()
if not self.importCodeValid or self.importCodeFetching then
return
end
local currentCurseBuffer = nil
local currentLinkBuffer = nil
if self.controls.appendNotReplace.state ~= true then
clearInputText()
else
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Aura" then
wipeTable(self.actor["Aura"])
self.actor["Aura"] = { }
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Curse" then
currentCurseBuffer = self.controls.editCurses.buf
self.controls.editCurses:SetText("") --curses do not play nicely with append atm, need to fix
wipeTable(self.actor["Curse"])
self.actor["Curse"] = { }
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Warcry Skills" then
wipeTable(self.actor["Warcry"])
self.actor["Warcry"] = { }
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Link Skills" then
-- only one link can be applied at a time anyway
currentLinkBuffer = self.controls.editLinks.buf
self.controls.editLinks:SetText("")
wipeTable(self.actor["Link"])
self.actor["Link"] = { }
end
end
-- Parse the XML
local dbXML, errMsg = common.xml.ParseXML(self.importCodeXML)
if not dbXML then
launch:ShowErrMsg("^1Error loading '%s': %s", fileName, errMsg)
return
elseif dbXML[1].elem ~= "PathOfBuilding2" then
launch:ShowErrMsg("^1Error parsing '%s': 'PathOfBuilding2' root element missing", fileName)
return
end
-- Load data
for _, tabNode in ipairs(dbXML[1]) do
if type(tabNode) == "table" and tabNode.elem == "Party" then
for _, node in ipairs(tabNode) do
if node.elem == "ExportedBuffs" then
if not node.attrib.name then
ConPrintf("missing name")
elseif node.attrib.name == "PartyMemberStats" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Party Member Stats" then
if #self.controls.editPartyMemberStats.buf > 0 then
node[1] = self.controls.editPartyMemberStats.buf.."\n"..(node[1] or "")
end
self.controls.editPartyMemberStats:SetText(node[1] or "")
self:ParseBuffs(self.actor["modDB"], self.controls.editPartyMemberStats.buf, "PartyMemberStats", self.actor["output"])
end
elseif node.attrib.name == "Aura" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Aura" then
if #self.controls.editAuras.buf > 0 then
node[1] = self.controls.editAuras.buf.."\n"..(node[1] or "")
end
self.controls.editAuras:SetText(node[1] or "")
self:ParseBuffs(self.actor["Aura"], self.controls.editAuras.buf, "Aura", self.controls.simpleAuras)
end
elseif node.attrib.name == "Curse" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Curse" then
if #self.controls.editCurses.buf > 0 then
node[1] = self.controls.editCurses.buf.."\n"..(node[1] or "")
end
if currentCurseBuffer and node[1] =="--- Curse Limit ---\n1" then
node[1] = currentCurseBuffer
end
self.controls.editCurses:SetText(node[1] or "")
self:ParseBuffs(self.actor["Curse"], self.controls.editCurses.buf, "Curse", self.controls.simpleCurses)
end
elseif node.attrib.name == "Warcry Skills" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Warcry Skills" then
if #self.controls.editWarcries.buf > 0 then
node[1] = self.controls.editWarcries.buf.."\n"..(node[1] or "")
end
self.controls.editWarcries:SetText(node[1] or "")
self:ParseBuffs(self.actor["Warcry"], self.controls.editWarcries.buf, "Warcry", self.controls.simpleWarcries)
end
elseif node.attrib.name == "Link Skills" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "Link Skills" then
if #self.controls.editLinks.buf > 0 then
node[1] = self.controls.editLinks.buf.."\n"..(node[1] or "")
end
if currentLinkBuffer and (not node[1] or node[1] == "") then
node[1] = currentLinkBuffer
end
self.controls.editLinks:SetText(node[1] or "")
self:ParseBuffs(self.actor["Link"], self.controls.editLinks.buf, "Link", self.controls.simpleLinks)
end
elseif node.attrib.name == "EnemyConditions" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyConditions" then
if #self.controls.enemyCond.buf > 0 then
node[1] = self.controls.enemyCond.buf.."\n"..(node[1] or "")
end
self.controls.enemyCond:SetText(node[1] or "")
end
elseif node.attrib.name == "EnemyMods" then
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyMods" then
if #self.controls.enemyMods.buf > 0 then
node[1] = self.controls.enemyMods.buf.."\n"..(node[1] or "")
end
self.controls.enemyMods:SetText(node[1] or "")
end
end
end
end
if partyDestinations[self.controls.importCodeDestination.selIndex] == "All" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyConditions" or partyDestinations[self.controls.importCodeDestination.selIndex] == "EnemyMods" then
wipeTable(self.enemyModList)
self.enemyModList = new("ModList")
self:ParseBuffs(self.enemyModList, self.controls.enemyCond.buf, "EnemyConditions")
self:ParseBuffs(self.enemyModList, self.controls.enemyMods.buf, "EnemyMods", self.controls.simpleEnemyMods)
end
self.build.buildFlag = true
break
end
end
end
self.controls.importCodeIn = new("EditControl", {"TOPLEFT",self.controls.importCodeHeader,"BOTTOMLEFT"}, {0, 4, 328, theme.buttonHeight}, "", nil, nil, nil, importCodeHandle)
self.controls.importCodeIn.width = function()
return (self.width > 880) and 328 or (self.width / 2 - 100)
end
self.controls.importCodeIn.enterFunc = function()
if self.importCodeValid then
self.controls.importCodeGo.onClick()
end
end
self.controls.importCodeState = new("LabelControl", {"LEFT",self.controls.importCodeIn,"RIGHT"}, {8, 0, 0, theme.stringHeight})
self.controls.importCodeState.label = function()
return self.importCodeDetail or ""
end
self.controls.importCodeDestination = new("DropDownControl", {"TOPLEFT",self.controls.importCodeIn,"BOTTOMLEFT"}, {0, 4, 160, theme.buttonHeight}, partyDestinations)
self.controls.importCodeDestination.tooltipText = "Destination for Import/clear\nCurrently Links Skills do not export"
self.controls.importCodeGo = new("ButtonControl", {"LEFT",self.controls.importCodeDestination,"RIGHT"}, {8, 0, 160, theme.buttonHeight}, "Import", function()
local importCodeFetching = false
if self.importCodeSite and not self.importCodeXML then
self.importCodeFetching = true
local selectedWebsite = buildSites.websiteList[self.importCodeSite]
buildSites.DownloadBuild(self.controls.importCodeIn.buf, selectedWebsite, function(isSuccess, data)
self.importCodeFetching = false
if not isSuccess then
self.importCodeDetail = colorCodes.NEGATIVE..data
self.importCodeValid = false
else
importCodeHandle(data)
finishImport()
end
end)
return
end
finishImport()
end)
self.controls.importCodeGo.enabled = function()
return (self.importCodeValid and not self.importCodeFetching)
end
self.controls.importCodeGo.enterFunc = function()
if self.importCodeValid then
self.controls.importCodeGo.onClick()
end
end
self.controls.appendNotReplace = new("CheckBoxControl", {"LEFT",self.controls.importCodeGo,"RIGHT"}, {60, 0, theme.buttonHeight}, "Append", function(state)
end, "This sets the import button to append to the current party lists instead of replacing them (curses will still replace)", false)
self.controls.appendNotReplace.x = function()
return (self.width > theme.widthThreshold1) and 60 or (-276)
end
self.controls.appendNotReplace.y = function()
return (self.width > theme.widthThreshold1) and 0 or 24
end
self.controls.clear = new("ButtonControl", {"LEFT",self.controls.appendNotReplace,"RIGHT"}, {8, 0, 160, theme.buttonHeight}, "Clear", function()
clearInputText()
wipeTable(self.enemyModList)
self.enemyModList = new("ModList")
self.build.buildFlag = true
end)
self.controls.clear.tooltipText = "^7Clears all the party tab imported data"
self.controls.ShowAdvanceTools = new("CheckBoxControl", {"TOPLEFT",self.controls.importCodeDestination,"BOTTOMLEFT"}, {140, 4, theme.buttonHeight}, "^7Show Advanced Info", function(state)
end, "This shows the advanced info like what stats each aura/curse etc are adding, as well as enables the ability to edit them without a re-export\nDo not edit any boxes unless you know what you are doing, use copy/paste or import instead", false)
self.controls.ShowAdvanceTools.y = function()
return (self.width > theme.widthThreshold1) and 4 or 28
end
self.controls.removeEffects = new("ButtonControl", {"LEFT",self.controls.ShowAdvanceTools,"RIGHT"}, {8, 0, 160, theme.buttonHeight}, "Disable Party Effects", function()
wipeTable(self.actor)
wipeTable(self.enemyModList)
self.actor = { Aura = {}, Curse = {}, Warcry = { }, Link = {}, modDB = new("ModDB"), output = { } }
self.actor.modDB.actor = self.actor
self.enemyModList = new("ModList")
self.build.buildFlag = true
end)
self.controls.removeEffects.tooltipText = "^7Removes the effects of the supports, without removing the data\nUse \"rebuild all\" to apply the effects again"
self.controls.rebuild = new("ButtonControl", {"LEFT",self.controls.removeEffects,"RIGHT"}, {8, 0, 160, theme.buttonHeight}, "^7Rebuild All", function()
wipeTable(self.actor)
wipeTable(self.enemyModList)
self.actor = { Aura = {}, Curse = {}, Warcry = { }, Link = {}, modDB = new("ModDB"), output = { } }
self.actor.modDB.actor = self.actor
self.enemyModList = new("ModList")
self:ParseBuffs(self.actor["modDB"], self.controls.editPartyMemberStats.buf, "PartyMemberStats", self.actor["output"])
self:ParseBuffs(self.actor["Aura"], self.controls.editAuras.buf, "Aura", self.controls.simpleAuras)
self:ParseBuffs(self.actor["Curse"], self.controls.editCurses.buf, "Curse", self.controls.simpleCurses)
self:ParseBuffs(self.actor["Warcry"], self.controls.editWarcries.buf, "Warcry", self.controls.simpleWarcries)
self:ParseBuffs(self.actor["Link"], self.controls.editLinks.buf, "Link", self.controls.simpleLinks)
self:ParseBuffs(self.enemyModList, self.controls.enemyCond.buf, "EnemyConditions")
self:ParseBuffs(self.enemyModList, self.controls.enemyMods.buf, "EnemyMods", self.controls.simpleEnemyMods)
self.build.buildFlag = true
end)
self.controls.rebuild.tooltipText = "^7Reparse all the inputs incase they have been disabled or they have changed since loading the build or importing"
self.controls.rebuild.x = function()
return (self.width > theme.widthThreshold1) and 8 or (-328)
end
self.controls.rebuild.y = function()
return (self.width > theme.widthThreshold1) and 0 or 24
end
self.controls.editAurasLabel = new("LabelControl", {"TOPLEFT",self.controls.ShowAdvanceTools,"TOPLEFT"}, {-140, 40, 0, theme.stringHeight}, "^7Auras")
self.controls.editAurasLabel.y = function()
return 36 + ((self.width <= theme.widthThreshold1) and 24 or 0)
end
self.controls.editAuras = new("EditControl", {"TOPLEFT",self.controls.editAurasLabel,"TOPLEFT"}, {0, 18, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.editAuras.width = function()
return self.width / 2 - 16
end
self.controls.editAuras.height = function()
return (self.controls.editWarcries.hasFocus or self.controls.editLinks.hasFocus) and theme.bufferHeightSmall or theme.bufferHeightLeft()
end
self.controls.editAuras.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleAuras = new("LabelControl", {"TOPLEFT",self.controls.editAurasLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "")
self.controls.simpleAuras.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self.controls.editWarcriesLabel = new("LabelControl", {"TOPLEFT",self.controls.editAurasLabel,"BOTTOMLEFT"}, {0, 8, 0, theme.stringHeight}, "^7Warcry Skills")
self.controls.editWarcriesLabel.y = function()
return self.controls.ShowAdvanceTools.state and (self.controls.editAuras.height() + 8) or (theme.lineCounter(self.controls.simpleAuras.label) + 4)
end
self.controls.editWarcries = new("EditControl", {"TOPLEFT",self.controls.editWarcriesLabel,"TOPLEFT"}, {0, 18, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.editWarcries.width = function()
return self.width / 2 - 16
end
self.controls.editWarcries.height = function()
return (self.controls.editWarcries.hasFocus and theme.bufferHeightLeft() or theme.bufferHeightSmall)
end
self.controls.editWarcries.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleWarcries = new("LabelControl", {"TOPLEFT",self.controls.editWarcriesLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "")
self.controls.simpleWarcries.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self.controls.editLinksLabel = new("LabelControl", {"TOPLEFT",self.controls.editWarcriesLabel,"BOTTOMLEFT"}, {0, 8, 0, theme.stringHeight}, "^7Link Skills")
self.controls.editLinksLabel.y = function()
return self.controls.ShowAdvanceTools.state and (self.controls.editWarcries.height() + 8) or (theme.lineCounter(self.controls.simpleWarcries.label) + 4)
end
self.controls.editLinks = new("EditControl", {"TOPLEFT",self.controls.editLinksLabel,"TOPLEFT"}, {0, 18, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.editLinks.width = function()
return self.width / 2 - 16
end
self.controls.editLinks.height = function()
return (self.controls.editLinks.hasFocus and theme.bufferHeightLeft() or theme.bufferHeightSmall)
end
self.controls.editLinks.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleLinks = new("LabelControl", {"TOPLEFT",self.controls.editLinksLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "")
self.controls.simpleLinks.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self.controls.editPartyMemberStatsLabel = new("LabelControl", {"TOPLEFT",self.controls.notesDesc,"TOPRIGHT"}, {8, 0, 0, theme.stringHeight}, "^7Party Member Stats")
self.controls.editPartyMemberStats = new("EditControl", {"TOPLEFT",self.controls.editPartyMemberStatsLabel,"BOTTOMLEFT"}, {0, 2, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.editPartyMemberStats.width = function()
return self.width / 2 - 16
end
self.controls.editPartyMemberStats.height = function()
return (self.controls.editPartyMemberStats.hasFocus and (self.height - theme.bufferHeightRight) or theme.bufferHeightSmall)
end
self.controls.editPartyMemberStats.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.enemyCondLabel = new("LabelControl", {"TOPLEFT",self.controls.editPartyMemberStatsLabel,"BOTTOMLEFT"}, {0, 8, 0, theme.stringHeight}, "^7Enemy Conditions")
self.controls.enemyCondLabel.y = function()
return self.controls.ShowAdvanceTools.state and (self.controls.editPartyMemberStats.height() + 8) or 4
end
self.controls.enemyCond = new("EditControl", {"TOPLEFT",self.controls.enemyCondLabel,"BOTTOMLEFT"}, {0, 2, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.enemyCond.width = function()
return self.width / 2 - 16
end
self.controls.enemyCond.height = function()
return (self.controls.enemyCond.hasFocus and (self.height - theme.bufferHeightRight) or theme.bufferHeightSmall)
end
self.controls.enemyCond.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleEnemyCond = new("LabelControl", {"TOPLEFT",self.controls.enemyCondLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "^7---------------------------\n")
self.controls.simpleEnemyCond.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self.controls.enemyModsLabel = new("LabelControl", {"TOPLEFT",self.controls.enemyCondLabel,"BOTTOMLEFT"}, {0, 8, 0, theme.stringHeight}, "^7Enemy Modifiers")
self.controls.enemyModsLabel.y = function()
return self.controls.ShowAdvanceTools.state and (self.controls.enemyCond.height() + 8) or (theme.lineCounter(self.controls.simpleEnemyCond.label) + 4)
end
self.controls.enemyMods = new("EditControl", {"TOPLEFT",self.controls.enemyModsLabel,"BOTTOMLEFT"}, {0, 2, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.enemyMods.width = function()
return self.width / 2 - 16
end
self.controls.enemyMods.height = function()
return (self.controls.enemyMods.hasFocus and (self.height - theme.bufferHeightRight) or theme.bufferHeightSmall)
end
self.controls.enemyMods.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleEnemyMods = new("LabelControl", {"TOPLEFT",self.controls.enemyModsLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "\n")
self.controls.simpleEnemyMods.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self.controls.editCursesLabel = new("LabelControl", {"TOPLEFT",self.controls.enemyModsLabel,"BOTTOMLEFT"}, {0, 8, 0, theme.stringHeight}, "^7Curses")
self.controls.editCursesLabel.y = function()
return self.controls.ShowAdvanceTools.state and (self.controls.enemyMods.height() + 8) or (theme.lineCounter(self.controls.simpleEnemyMods.label) + 4)
end
self.controls.editCurses = new("EditControl", {"TOPLEFT",self.controls.editCursesLabel,"BOTTOMLEFT"}, {0, 2, 0, 0}, "", nil, "^%C\t\n", nil, nil, 14, true)
self.controls.editCurses.width = function()
return self.width / 2 - 16
end
self.controls.editCurses.height = function()
return (self.controls.enemyCond.hasFocus or self.controls.enemyMods.hasFocus or self.controls.editPartyMemberStats.hasFocus) and theme.bufferHeightSmall or (self.height - theme.bufferHeightRight)
end
self.controls.editCurses.shown = function()
return self.controls.ShowAdvanceTools.state
end
self.controls.simpleCurses = new("LabelControl", {"TOPLEFT",self.controls.editCursesLabel,"TOPLEFT"}, {0, 18, 0, theme.stringHeight}, "")
self.controls.simpleCurses.shown = function()
return not self.controls.ShowAdvanceTools.state
end
self:SelectControl(self.controls.editAuras)
end)
function PartyTabClass:Load(xml, fileName)
for _, node in ipairs(xml) do
if node.elem == "ImportedBuffs" then
if not node.attrib.name then
ConPrintf("missing name")
elseif node.attrib.name == "PartyMemberStats" then
self.controls.editPartyMemberStats:SetText(node[1] or "")
self:ParseBuffs(self.actor["modDB"], node[1] or "", "PartyMemberStats", self.actor["output"])
elseif node.attrib.name == "Aura" then
self.controls.editAuras:SetText(node[1] or "")
self:ParseBuffs(self.actor["Aura"], node[1] or "", "Aura", self.controls.simpleAuras)
elseif node.attrib.name == "Curse" then
self.controls.editCurses:SetText(node[1] or "")
self:ParseBuffs(self.actor["Curse"], node[1] or "", "Curse", self.controls.simpleCurses)
elseif node.attrib.name == "Warcry Skills" then
self.controls.editWarcries:SetText(node[1] or "")
self:ParseBuffs(self.actor["Warcry"], node[1] or "", "Warcry", self.controls.simpleWarcries)
elseif node.attrib.name == "Link Skills" then
self.controls.editLinks:SetText(node[1] or "")
self:ParseBuffs(self.actor["Link"], node[1] or "", "Link", self.controls.simpleLinks)
elseif node.attrib.name == "EnemyConditions" then
self.controls.enemyCond:SetText(node[1] or "")
self:ParseBuffs(self.enemyModList, node[1] or "", "EnemyConditions")
elseif node.attrib.name == "EnemyMods" then
self.controls.enemyMods:SetText(node[1] or "")
self:ParseBuffs(self.enemyModList, node[1] or "", "EnemyMods", self.controls.simpleEnemyMods)
end
elseif node.elem == "ExportedBuffs" then
if not node.attrib.name then
ConPrintf("missing name")
end
--if node.attrib.name ~= "EnemyConditions" and node.attrib.name ~= "EnemyMods" then
--- self:ParseBuffs(self.buffExports, node[1] or "", node.attrib.name)
--end
--self:ParseBuffs(self.buffExports, node[1] or "", "PartyMemberStats")
--self:ParseBuffs(self.buffExports, node[1] or "", "Aura")
--self:ParseBuffs(self.buffExports, node[1] or "", "Curse")
--self:ParseBuffs(self.buffExports, node[1] or "", "Warcry")
--self:ParseBuffs(self.buffExports, node[1] or "", "Link")
--self:ParseBuffs(self.buffExports, node[1] or "", "EnemyConditions")
--self:ParseBuffs(self.buffExports, node[1] or "", "EnemyMods")
end
end
self.lastContent.PartyMemberStats = self.controls.editPartyMemberStats.buf
self.lastContent.Aura = self.controls.editAuras.buf
self.lastContent.Curse = self.controls.editCurses.buf
self.lastContent.Warcry = self.controls.editWarcries.buf
self.lastContent.Link = self.controls.editLinks.buf
self.lastContent.EnemyCond = self.controls.enemyCond.buf
self.lastContent.EnemyMods = self.controls.enemyMods.buf
self.lastContent.EnableExportBuffs = self.enableExportBuffs
self.controls.importCodeDestination:SelByValue(xml.attrib.destination or "All")
self.controls.appendNotReplace.state = xml.attrib.append == "true"
self.controls.ShowAdvanceTools.state = xml.attrib.ShowAdvanceTools == "true"
self.lastContent.showAdvancedTools = self.controls.ShowAdvanceTools.state
end
function PartyTabClass:Save(xml)
local child
if self.controls.editPartyMemberStats.buf and self.controls.editPartyMemberStats.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "PartyMemberStats" } }
t_insert(child, self.controls.editPartyMemberStats.buf)
t_insert(xml, child)
end
if self.controls.editAuras.buf and self.controls.editAuras.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "Aura" } }
t_insert(child, self.controls.editAuras.buf)
t_insert(xml, child)
end
if self.controls.editCurses.buf and self.controls.editCurses.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "Curse" } }
t_insert(child, self.controls.editCurses.buf)
t_insert(xml, child)
end
if self.controls.editWarcries.buf and self.controls.editWarcries.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "Warcry Skills" } }
t_insert(child, self.controls.editWarcries.buf)
t_insert(xml, child)
end
if self.controls.editLinks.buf and self.controls.editLinks.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "Link Skills" } }
t_insert(child, self.controls.editLinks.buf)
t_insert(xml, child)
end
if self.controls.enemyCond.buf and self.controls.enemyCond.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "EnemyConditions" } }
t_insert(child, self.controls.enemyCond.buf)
t_insert(xml, child)
end
if self.controls.enemyMods.buf and self.controls.enemyMods.buf ~= "" then
child = { elem = "ImportedBuffs", attrib = { name = "EnemyMods" } }
t_insert(child, self.controls.enemyMods.buf)
t_insert(xml, child)
end
local exportString = self:exportBuffs("PlayerMods")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "PartyMemberStats" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("Aura")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "Aura" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("Curse")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "Curse" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("Warcry")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "Warcry Skills" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("Link")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "Link Skills" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("EnemyConditions")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "EnemyConditions" } }
t_insert(child, exportString)
t_insert(xml, child)
end
exportString = self:exportBuffs("EnemyMods")
if exportString ~= "" then
child = { elem = "ExportedBuffs", attrib = { name = "EnemyMods" } }
t_insert(child, exportString)
t_insert(xml, child)
end
self.lastContent.PartyMemberStats = self.controls.editPartyMemberStats.buf
self.lastContent.Aura = self.controls.editAuras.buf
self.lastContent.Curse = self.controls.editCurses.buf
self.lastContent.Warcry = self.controls.editWarcries.buf
self.lastContent.Link = self.controls.editLinks.buf
self.lastContent.EnemyCond = self.controls.enemyCond.buf
self.lastContent.EnemyMods = self.controls.enemyMods.buf
self.lastContent.EnableExportBuffs = self.enableExportBuffs
xml.attrib = {
destination = self.controls.importCodeDestination.list[self.controls.importCodeDestination.selIndex],
append = tostring(self.controls.appendNotReplace.state),
ShowAdvanceTools = tostring(self.controls.ShowAdvanceTools.state)
}
self.lastContent.showAdvancedTools = self.controls.ShowAdvanceTools.state
end
function PartyTabClass:Draw(viewPort, inputEvents)
self.x = viewPort.x
self.y = viewPort.y
self.width = viewPort.width
self.height = viewPort.height
for id, event in ipairs(inputEvents) do
if event.type == "KeyDown" then
if event.key == "z" and IsKeyDown("CTRL") then
if self.controls.editAuras.hasFocus then
self.controls.editAuras:Undo()
elseif self.controls.editCurses.hasFocus then
self.controls.editCurses:Undo()
elseif self.controls.editLinks.hasFocus then
self.controls.editLinks:Undo()
elseif self.controls.editPartyMemberStats.hasFocus then
self.controls.editPartyMemberStats:Undo()
end
elseif event.key == "y" and IsKeyDown("CTRL") then
if self.controls.editAuras.hasFocus then
self.controls.editAuras:Redo()
elseif self.controls.editCurses.hasFocus then
self.controls.editCurses:Redo()
elseif self.controls.editLinks.hasFocus then
self.controls.editLinks:Redo()
elseif self.controls.editPartyMemberStats.hasFocus then
self.controls.editPartyMemberStats:Redo()
end
end
end
end
self:ProcessControlsInput(inputEvents, viewPort)
main:DrawBackground(viewPort)
self:DrawControls(viewPort)
self.modFlag = (self.lastContent.Aura ~= self.controls.editAuras.buf
or self.lastContent.Curse ~= self.controls.editCurses.buf
or self.lastContent.Warcry ~= self.controls.editWarcries.buf
or self.lastContent.Link ~= self.controls.editLinks.buf
or self.lastContent.PartyMemberStats ~= self.controls.editPartyMemberStats.buf
or self.lastContent.EnemyCond ~= self.controls.enemyCond.buf
or self.lastContent.EnemyMods ~= self.controls.enemyMods.buf
or self.lastContent.EnableExportBuffs ~= self.enableExportBuffs
or self.lastContent.showAdvancedTools ~= self.controls.ShowAdvanceTools.state)
end
function PartyTabClass:ParseBuffs(list, buf, buffType, label)
if buffType == "EnemyConditions" then
for line in buf:gmatch("([^\n]*)\n?") do
if line ~= "" then
list:NewMod(line:gsub("Condition:", "Condition:Party:"), "FLAG", true, "Party")
end
end
elseif buffType == "EnemyMods" then
local enemyModList = {}
local currentName
for line in buf:gmatch("([^\n]*)\n?") do
if not line:find("|") then
currentName = line
if label and currentName ~= "" then
enemyModList[currentName] = enemyModList[currentName] or {}
end
else
local mod = modLib.parseFormattedSourceMod(line)
if mod then
mod.source = "Party"..mod.source
list:AddMod(mod)
if label then
t_insert(enemyModList[currentName], {mod.value, mod.type})
end
end
end
end
if label then
local count = 0
label.label = "^7---------------------------"
for modName, modList in pairs(enemyModList) do
label.label = label.label.."\n"..modName..":"
count = count + 1
for _, mod in ipairs(modList) do
label.label = label.label.." "..(mod[1] and "True" or mod[1]).." "..(mod[2] == "FLAG" and "" or mod[2])..", "
end
end
if count > 0 then
label.label = label.label.."\n---------------------------\n"
else
label.label = label.label.."\n"
end
end
elseif buffType == "PartyMemberStats" then
if not list then
else
for line in buf:gmatch("([^\n]*)\n?") do
if line:find("=") then
-- label is output for this type, as a special case
if line:match("%.") then
local k1, k2, v = line:match("([%w ]-%w+)%.([%w ]-%w+)=(.+)")
label[k1] = {[k2] = tonumber(v)}
elseif line:match("|") then
local k, tags, v = line:match("([%w ]-%w+)|(.+)=(.+)")
v = tonumber(v)
for tag in tags:gmatch("([^|]*)|?") do
if tag == "percent" then
v = v / 100
elseif tag == "max" then
v = m_max(v, label[k] or 1)
end
end
label[k] = v
else
local k, v = line:match("([%w ]-%w+)=(.+)")
label[k] = tonumber(v)
end
elseif line ~= "" then
list:NewMod(line, "FLAG", true, "Party")
end
end
end
else
local mode = "Name"
if buffType == "Curse" then
mode = "CurseLimit"
end
local currentName
local currentEffect
local isMark
local currentModType = (buffType == "Link") and "Link" or (buffType == "Warcry") and "Warcry" or "Unknown"
for line in buf:gmatch("([^\n]*)\n?") do
if line ~= "---" and line:match("%-%-%-") then
-- comment but not divider, skip the line
elseif mode == "CurseLimit" and line ~= "" then
list.limit = tonumber(line)
mode = "Name"
elseif mode == "Name" and line ~= "" then
currentName = line:gsub("_Debuff", "")
currentEffect = 0
if line == "extraAura" or line == "otherEffects" then
currentModType = line
mode = "Stats"
else
mode = "Effect"
currentModType = "Unknown"
end
elseif mode == "Effect" then
currentEffect = tonumber(line)
if buffType == "Curse" then
mode = "isMark"
else
mode = "Stats"
end
elseif mode == "isMark" then
isMark = line=="true"
mode = "Stats"
elseif line == "---" then
mode = "Name"
else
if line:find("|") and currentName ~= "SKIP" and not line:find("MinionModifier|LIST") then
if currentModType == "otherEffects" then
currentName, currentEffect, line = line:match("([%w ']-%w+)|(%w+)|(.+)")
end
local mod = modLib.parseFormattedSourceMod(line)
if mod then
for _, tag in ipairs(mod) do
if tag.type == "GlobalEffect" and currentModType ~= "Link" and currentModType ~= "Warcry" and currentModType ~= "otherEffects" then
currentModType = tag.effectType
end
end
list[currentModType] = list[currentModType] or {}
local listElement = list[currentModType]
if currentName:sub(1,4) == "Vaal" then
list[currentModType]["Vaal"] = list[currentModType]["Vaal"] or {}
listElement = list[currentModType]["Vaal"]
end
if not listElement[currentName] then
listElement[currentName] = {
modList = new("ModList"),
effectMult = currentEffect
}
if isMark then
listElement[currentName].isMark = true
end
elseif listElement[currentName].effectMult ~= currentEffect then
if listElement[currentName].effectMult < currentEffect then
listElement[currentName] = {
modList = new("ModList"),
effectMult = currentEffect
}
else
currentName = "SKIP"
end
end
if currentName ~= "SKIP" then
if mod.source:match("Item") then
local oldItem
oldItem, mod.source = mod.source:match("Item:(%d+):(.+)")
mod.source = "Party - "..mod.source
end
if mod.source:match("Skill") then
local skillId = mod.source:match("Skill:(.+)")
if not data.skills[skillId] then
local minimisedName = currentName:gsub(" %l",string.upper):gsub(" ","")
if data.skills[minimisedName] then
mod.source = "Skill:"..minimisedName
else
mod.source = skillId
end
end
end
if buffType == "Link" then
mod.name = mod.name:gsub("Parent", "PartyMember")
for _, modTag in ipairs(mod) do
if modTag.actor and modTag.actor == "parent" then
modTag.actor = "partyMembers"
end
end
end
listElement[currentName].modList:AddMod(mod)
end
end
end
end
end
if label then
if buffType == "Aura" then
local labelList = {}
label.label = "^7---------------------------\n"
for aura, auraMod in pairs(list["Aura"] or {}) do
if aura ~= "Vaal" then
t_insert(labelList, aura..": "..auraMod.effectMult.."%\n")
end
end
if list["Aura"] and list["Aura"]["Vaal"] then
for aura, auraMod in pairs(list["Aura"]["Vaal"]) do
t_insert(labelList, aura..": "..auraMod.effectMult.."%\n")
end
end
for aura, auraMod in pairs(list["AuraDebuff"] or {}) do
if not list["Aura"] or not list["Aura"][aura] then
if aura ~= "Vaal" then
t_insert(labelList, aura..": "..auraMod.effectMult.."%\n")
end
end
end
if list["AuraDebuff"] and list["AuraDebuff"]["Vaal"] then
if not list["Aura"] or not list["Aura"]["Vaal"] or not list["Aura"]["Vaal"][aura] then
for aura, auraMod in pairs(list["AuraDebuff"]["Vaal"]) do
t_insert(labelList, aura..": "..auraMod.effectMult.."%\n")
end
end
end
if #labelList > 0 then
table.sort(labelList)
label.label = label.label..table.concat(labelList)
label.label = label.label.."---------------------------\n"
end
if list["extraAura"] and list["extraAura"]["extraAura"] then
label.label = label.label.."extraAuras:\n"
for _, auraMod in ipairs(list["extraAura"]["extraAura"].modList) do
label.label = label.label.." "..(auraMod.type == "FLAG" and "" or (auraMod.type.." "))..auraMod.name..": "..tostring(auraMod.value).."\n"
end
label.label = label.label.."---------------------------\n"
end
if list["otherEffects"] then
label.label = label.label.."otherEffects:\n"
for buffName, buff in pairs(list["otherEffects"]) do
for _, auraMod in ipairs(list["otherEffects"][buffName].modList) do
label.label = label.label.." "..(auraMod.type == "FLAG" and "" or (auraMod.type.." "))..auraMod.name..": "..tostring(auraMod.value).."\n"
end
end
label.label = label.label.."---------------------------\n"
end
elseif buffType == "Warcry" then
local labelList = {}
for warcry, warcryMod in pairs(list["Warcry"] or {}) do
t_insert(labelList, warcry..": "..warcryMod.effectMult.."%\n")
end
if #labelList > 0 then
table.sort(labelList)
label.label = "^7---------------------------\n"..table.concat(labelList).."---------------------------\n"
else
label.label = ""
end
elseif buffType == "Link" then
local labelList = {}
for link, linkMod in pairs(list["Link"] or {}) do
t_insert(labelList, link..": "..linkMod.effectMult.."%\n")
end
if #labelList > 0 then
table.sort(labelList)
label.label = "^7---------------------------\n"..table.concat(labelList).."---------------------------\n"
else
label.label = ""
end
elseif buffType == "Curse" then
local labelList = {}
for curse, curseMod in pairs(list["Curse"] or {}) do
t_insert(labelList, curse..": "..curseMod.effectMult.."%\n")
end
if #labelList > 0 then
table.sort(labelList)
label.label = "^7---------------------------\n"..table.concat(labelList).."---------------------------\n"
else
label.label = ""
end
end
end
end
end
function PartyTabClass:setBuffExports(buffExports)
if not self.enableExportBuffs then
return
end
wipeTable(self.buffExports)
self.buffExports = copyTable(buffExports, true)
end
function PartyTabClass:exportBuffs(buffType)
if not self.enableExportBuffs or not self.buffExports or not self.buffExports[buffType] then
return ""
end
if self.buffExports[buffType].ConvertedToText then
return self.buffExports[buffType].string
end
local buf = ((buffType == "Curse") and ("--- Curse Limit ---\n" .. tostring(self.buffExports["CurseLimit"]))) or ""
for buffName, buff in pairs(self.buffExports[buffType]) do
if buffName ~= "extraAura" or #buff.modList > 0 then
if #buf > 0 then
buf = buf.."\n"
end
buf = buf..buffName
if buffType == "Curse" then
buf = buf.."\n"..tostring(buff.effectMult * 100)
if buff.isMark then
buf = buf.."\ntrue"
else
buf = buf.."\nfalse"
end
elseif buffType == "Link" or buffType == "Warcry" or buffType == "Aura" and buffName ~= "extraAura" and buffName ~= "otherEffects" then
buf = buf.."\n"..tostring(buff.effectMult * 100)
end
if buffType == "Aura" and buffName == "otherEffects" then
for innerBuffName, innerBuff in pairs(buff) do
for _, mod in ipairs(innerBuff.modList) do
buf = buf.."\n"..innerBuffName.."|"..tostring(innerBuff.effectMult * 100).."|"..modLib.formatSourceMod(mod)
end
end
buf = buf.."\n---"
elseif buffType == "Curse" or buffType == "Aura" or buffType == "Warcry" or buffType == "Link" then
for _, mod in ipairs(buff.modList) do
buf = buf.."\n"..modLib.formatSourceMod(mod)
end
buf = buf.."\n---"
elseif buffType == "EnemyMods" then
if buff.MultiStat then
for _, buffInner in ipairs(buff) do
buf = buf.."\n"..modLib.formatSourceMod(buffInner)
end
else
buf = buf.."\n"..modLib.formatSourceMod(buff)
end
end
end
end
wipeTable(self.buffExports[buffType])
self.buffExports[buffType] = { ConvertedToText = true }
self.buffExports[buffType].string = buf
return buf
end