Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Classes/GemSelectControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,12 @@ function GemSelectClass:AddGemTooltip(gemInstance)
self.tooltip:AddLine(fontSizeBig, colorCodes.UNIQUE .. line, "FONTIN SC ITALIC")
end
end
-- Author note (Shift+Right-Click to set/edit) emitted into the PoE2 .build export.
self.tooltip:AddSeparator(10)
self.tooltip:AddLine(14, colorCodes.TIP.."Shift + Right-Click to add a build note (PoE2 .build export)")
if gemInstance.note and gemInstance.note ~= "" then
self.tooltip:AddLine(14, "^7Note: "..gemInstance.note)
end
end

function GemSelectClass:AddGrantedEffectInfo(gemInstance, grantedEffect, addReq)
Expand Down Expand Up @@ -863,6 +869,18 @@ function GemSelectClass:OnKeyDown(key, doubleClick)
self:ScrollSelIntoView()
end
end
elseif key == "RIGHTBUTTON" and IsKeyDown("SHIFT") then
-- Shift+Right-Click: edit the per-gem author note for the PoE2 .build export.
local gemList = self.skillsTab.displayGroup and self.skillsTab.displayGroup.gemList
local gemInstance = gemList and gemList[self.index]
if gemInstance then
local title = "Note: " .. ((gemInstance.nameSpec and gemInstance.nameSpec ~= "") and gemInstance.nameSpec or "Gem")
main:OpenNoteEditPopup(title, gemInstance.note, function(text)
gemInstance.note = text
self.skillsTab.build.modFlag = true
end)
end
return self
elseif key == "RETURN" or key == "RIGHTBUTTON" then
self.dropped = true
self:UpdateSortCache()
Expand Down
38 changes: 38 additions & 0 deletions src/Classes/ImportTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,24 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(
end
end

-- Path of Exile 2 BuildPlanner export
local BuildExportPoE2 = require("Modules/BuildExportPoE2")
self.controls.sectionPoE2Export = new("SectionControl", {"TOPLEFT",self.controls.sectionBuild,"BOTTOMLEFT",true}, {0, 18, 650, 112}, "Export to Path of Exile 2 BuildPlanner")
self.controls.poe2ExportDesc = new("LabelControl", {"TOPLEFT",self.controls.sectionPoE2Export,"TOPLEFT"}, {6, 14, 0, 16}, "^7Save this build as a .build file the in-game BuildPlanner can load.")
self.controls.poe2ExportDesc2 = new("LabelControl", {"TOPLEFT",self.controls.poe2ExportDesc,"BOTTOMLEFT"}, {0, 2, 0, 14}, "^xAAAAAATree specs, item sets and skill sets are exported as level-bracketed loadouts.")
self.controls.poe2ExportDesc3 = new("LabelControl", {"TOPLEFT",self.controls.poe2ExportDesc2,"BOTTOMLEFT"}, {0, 2, 0, 14}, "^xAAAAAAEdit each set's level range in its Manage popup.")
self.poe2ExportStatus = ""
self.controls.poe2ExportPath = new("EditControl", {"TOPLEFT",self.controls.poe2ExportDesc3,"BOTTOMLEFT"}, {0, 8, 560, 20}, BuildExportPoE2.DefaultPath(self.build), "Path", nil, 260)
self.controls.poe2ExportSave = new("ButtonControl", {"LEFT",self.controls.poe2ExportPath,"RIGHT"}, {8, 0, 80, 20}, "Save", function()
self:DoPoE2Export(BuildExportPoE2, self.controls.poe2ExportPath.buf)
end)
self.controls.poe2ExportSave.enabled = function()
return self.controls.poe2ExportPath.buf and self.controls.poe2ExportPath.buf ~= ""
end
self.controls.poe2ExportStatusLabel = new("LabelControl", {"TOPLEFT",self.controls.poe2ExportPath,"BOTTOMLEFT"}, {0, 4, 0, 14}, function()
return self.poe2ExportStatus or ""
end)

-- validate the status of the api the first time
self.api:ValidateAuth(function(valid, updateSettings)
if valid then
Expand All @@ -355,6 +373,26 @@ function ImportTabClass:SaveApiSettings()
main:SaveSettings()
end

function ImportTabClass:DoPoE2Export(Exporter, path)
local function doWrite()
local ok, err = Exporter.WriteFile(self.build, path)
if ok then
self.poe2ExportStatus = colorCodes.POSITIVE .. "Saved to " .. path
else
self.poe2ExportStatus = colorCodes.NEGATIVE .. (err or "Export failed")
main:OpenMessagePopup("Export Failed", err or "Unknown error")
end
end
-- Confirm overwrite if the file already exists.
local existing = io.open(path, "r")
if existing then
existing:close()
main:OpenConfirmPopup("Overwrite?", "A file already exists at:\n" .. path .. "\n\nOverwrite it?", "Overwrite", doWrite)
else
doWrite()
end
end

function ImportTabClass:Load(xml, fileName)
self.lastRealm = xml.attrib.lastRealm
self.controls.accountRealm:SelByValue(self.lastRealm or main.lastRealm or "PC", "id")
Expand Down
5 changes: 5 additions & 0 deletions src/Classes/ItemSetListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ local ItemSetListClass = newClass("ItemSetListControl", "ListControl", function(
return self.selValue ~= nil
end
self.controls.new = new("ButtonControl", {"RIGHT",self.controls.rename,"LEFT"}, {-4, 0, 60, 18}, "New", function()
local existing = { }
for _, id in ipairs(itemsTab.itemSetOrderList) do
t_insert(existing, itemsTab.itemSets[id])
end
local newSet = itemsTab:NewItemSet()
require("Modules/BuildExportPoE2").PresetNextLevels(existing, newSet)
self:RenameSet(newSet, true)
end)
end)
Expand Down
48 changes: 46 additions & 2 deletions src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,8 @@ function ItemsTabClass:Load(xml, dbFileName)
local itemSet = self:NewItemSet(tonumber(node.attrib.id))
itemSet.title = node.attrib.title
itemSet.useSecondWeaponSet = node.attrib.useSecondWeaponSet == "true"
itemSet.levelMin = tonumber(node.attrib.levelMin)
itemSet.levelMax = tonumber(node.attrib.levelMax)
for _, child in ipairs(node) do
if child.elem == "Slot" then
local slotName = child.attrib.name or ""
Expand Down Expand Up @@ -1056,7 +1058,7 @@ function ItemsTabClass:Save(xml)
end
for _, itemSetId in ipairs(self.itemSetOrderList) do
local itemSet = self.itemSets[itemSetId]
local child = { elem = "ItemSet", attrib = { id = tostring(itemSetId), title = itemSet.title, useSecondWeaponSet = tostring(itemSet.useSecondWeaponSet) } }
local child = { elem = "ItemSet", attrib = { id = tostring(itemSetId), title = itemSet.title, useSecondWeaponSet = tostring(itemSet.useSecondWeaponSet), levelMin = itemSet.levelMin and tostring(itemSet.levelMin) or nil, levelMax = itemSet.levelMax and tostring(itemSet.levelMax) or nil } }
for slotName, slot in pairs(self.slots) do
if not slot.nodeId then
t_insert(child, { elem = "Slot", attrib = { name = slotName, itemId = tostring(itemSet[slotName].selItemId), itemPbURL = itemSet[slotName].pbURL or "", active = itemSet[slotName].active and "true" }})
Expand Down Expand Up @@ -2053,7 +2055,49 @@ function ItemsTabClass:OpenItemSetManagePopup()
controls.sharedList = new("SharedItemSetListControl", nil, {155, 50, 300, 200}, self)
controls.setList.dragTargetList = { controls.sharedList }
controls.sharedList.dragTargetList = { controls.setList }
controls.close = new("ButtonControl", nil, {0, 260, 90, 20}, "Done", function()

-- Level bracket inputs for the .build (PoE2 BuildPlanner) export.
-- Bound to the row currently selected in the local item-set list.
local function clampLvl(buf)
local n = tonumber(buf)
if not n then return nil end
n = m_floor(n)
if n < 0 then n = 0 end
if n > 100 then n = 100 end
return n
end
local function selectedSet()
return self.itemSets[controls.setList.selValue]
end
controls.lvlLabel = new("LabelControl", nil, {-225, 260, 0, 16}, "^7.build export level range:")
controls.lvlMin = new("EditControl", nil, {-95, 260, 60, 20}, nil, nil, "%D", 3, function(buf)
local set = selectedSet()
if set then
set.levelMin = clampLvl(buf)
self.modFlag = true
end
end)
controls.lvlMin.tooltipText = "Lowest character level this item set applies to in the exported .build (1-100). Leave blank to auto-split across item sets."
controls.lvlDash = new("LabelControl", nil, {-30, 260, 0, 16}, "^7-")
controls.lvlMax = new("EditControl", nil, {30, 260, 60, 20}, nil, nil, "%D", 3, function(buf)
local set = selectedSet()
if set then
set.levelMax = clampLvl(buf)
self.modFlag = true
end
end)
controls.lvlMax.tooltipText = "Highest character level this item set applies to in the exported .build (1-100). Leave blank to auto-split across item sets."
local function refreshLvl()
local set = selectedSet()
controls.lvlMin:SetText(set and set.levelMin and tostring(set.levelMin) or "")
controls.lvlMax:SetText(set and set.levelMax and tostring(set.levelMax) or "")
end
controls.setList.OnSelClick = function(listSelf, index, value, doubleClick)
refreshLvl()
end
refreshLvl()

controls.close = new("ButtonControl", nil, {180, 260, 90, 20}, "Done", function()
main:ClosePopup()
end)
main:OpenPopup(630, 290, "Manage Item Sets", controls)
Expand Down
33 changes: 32 additions & 1 deletion src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,17 @@ function PassiveSpecClass:Init(treeVersion, convert)

-- Keys are node IDs, values are the replacement node
self.hashOverrides = { }

-- Author notes attached to allocated nodes (Shift+Right-Click on a node to
-- set one). Keyed by node id; emitted into the PoE2 .build export as the
-- node's additional_text.
self.nodeNotes = { }
end

function PassiveSpecClass:Load(xml, dbFileName)
self.title = xml.attrib.title
self.levelMin = tonumber(xml.attrib.levelMin)
self.levelMax = tonumber(xml.attrib.levelMax)
local weaponSets = {}
local url
for _, node in pairs(xml) do
Expand Down Expand Up @@ -134,6 +141,17 @@ function PassiveSpecClass:Load(xml, dbFileName)
for nodeId in node.attrib.nodes:gmatch("%d+") do
weaponSets[tonumber(nodeId)] = weaponSet
end
elseif node.elem == "Notes" then
for _, child in ipairs(node) do
if child.elem == "Note" and child.attrib.nodeId then
local nid = tonumber(child.attrib.nodeId)
-- Note text lives in the element body (preserves newlines, no XML attribute escaping headaches).
local text = type(child[1]) == "string" and child[1] or child.attrib.text
if nid and text and text ~= "" then
self.nodeNotes[nid] = text
end
end
end
end
end
end
Expand Down Expand Up @@ -248,7 +266,9 @@ function PassiveSpecClass:Save(xml)
ascendancyInternalId = tostring(ascendancyInternalId),
secondaryAscendClassId = tostring(self.curSecondaryAscendClassId),
nodes = table.concat(allocNodeIdList, ","),
masteryEffects = table.concat(masterySelections, ",")
masteryEffects = table.concat(masterySelections, ","),
levelMin = self.levelMin and tostring(self.levelMin) or nil,
levelMax = self.levelMax and tostring(self.levelMax) or nil,
}
t_insert(xml, {
-- Legacy format
Expand Down Expand Up @@ -298,6 +318,17 @@ function PassiveSpecClass:Save(xml)
end
t_insert(xml, overrides)

-- Per-node author notes (Shift+Right-Click on a node). Stored as element
-- body text so multi-line notes survive without XML attribute escaping.
local notesElem = { elem = "Notes" }
local hasNotes = false
for nodeId, note in pairs(self.nodeNotes) do
if note and note ~= "" then
hasNotes = true
t_insert(notesElem, { elem = "Note", attrib = { nodeId = tostring(nodeId) }, [1] = note })
end
end
if hasNotes then t_insert(xml, notesElem) end
end

function PassiveSpecClass:PostLoad()
Expand Down
1 change: 1 addition & 0 deletions src/Classes/PassiveSpecListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ local PassiveSpecListClass = newClass("PassiveSpecListControl", "ListControl", f
newSpec:SelectClass(treeTab.build.spec.curClassId)
newSpec:SelectAscendClass(treeTab.build.spec.curAscendClassId)
newSpec:SelectSecondaryAscendClass(treeTab.build.spec.curSecondaryAscendClassId)
require("Modules/BuildExportPoE2").PresetNextLevels(treeTab.specList, newSpec)
self:RenameSpec(newSpec, "New Tree", true)
end)
self:UpdateItemsTabPassiveTreeDropdown()
Expand Down
20 changes: 19 additions & 1 deletion src/Classes/PassiveTreeView.lua
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,16 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
elseif treeClick == "RIGHT" then
-- User right-clicked on a node
if hoverNode then
if hoverNode.alloc and (hoverNode.type == "Socket" or hoverNode.containJewelSocket) then
if IsKeyDown("SHIFT") then
-- Shift+Right-Click: open a popup to edit the per-node author note
-- (consumed by the PoE2 .build export as the node's additional_text).
local nodeId = hoverNode.id
local title = "Note: " .. (hoverNode.dn or hoverNode.name or "Passive")
main:OpenNoteEditPopup(title, spec.nodeNotes[nodeId], function(text)
spec.nodeNotes[nodeId] = text
build.modFlag = true
end)
elseif hoverNode.alloc and (hoverNode.type == "Socket" or hoverNode.containJewelSocket) then
local slot = build.itemsTab.sockets[hoverNode.id]
if slot:IsEnabled() then
-- User right-clicked a jewel socket, jump to the item page and focus the corresponding item slot control
Expand Down Expand Up @@ -1682,6 +1691,15 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, incSmallPassi
tooltip:AddLine(14, colorCodes.TIP.."Tip: Hold Ctrl to hide this tooltip.")
tooltip:AddLine(14, colorCodes.TIP.."Tip: Press Ctrl+C to copy this node's text.")
end
-- Per-node author note (Shift+Right-Click to set/edit) emitted into the PoE2 .build export.
if node.id and build.spec and build.spec.nodeNotes then
local existing = build.spec.nodeNotes[node.id]
tooltip:AddSeparator(10)
tooltip:AddLine(14, colorCodes.TIP.."Shift + Right-Click to add a build note (PoE2 .build export)")
if existing and existing ~= "" then
tooltip:AddLine(14, "^7Note: "..existing)
end
end
end

-- Helper function to check if a node is connected to weapon set nodes
Expand Down
8 changes: 7 additions & 1 deletion src/Classes/SkillSetListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ local SkillSetListClass = newClass("SkillSetListControl", "ListControl", functio
return self.selValue ~= nil
end
self.controls.new = new("ButtonControl", {"RIGHT",self.controls.rename,"LEFT"}, {-4, 0, 60, 18}, "New", function()
self:RenameSet(skillsTab:NewSkillSet(), true)
local existing = { }
for _, id in ipairs(skillsTab.skillSetOrderList) do
t_insert(existing, skillsTab.skillSets[id])
end
local newSet = skillsTab:NewSkillSet()
require("Modules/BuildExportPoE2").PresetNextLevels(existing, newSet)
self:RenameSet(newSet, true)
end)
end)

Expand Down
60 changes: 53 additions & 7 deletions src/Classes/SkillsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ function SkillsTabClass:LoadSkill(node, skillSetId)
end
gemInstance.level = tonumber(child.attrib.level)
gemInstance.quality = tonumber(child.attrib.quality)
-- Optional author note for the PoE2 .build export (Shift+Right-Click on the gem to set).
gemInstance.note = child.attrib.note
gemInstance.enabled = not child.attrib.enabled and true or child.attrib.enabled == "true"
gemInstance.enableGlobal1 = not child.attrib.enableGlobal1 or child.attrib.enableGlobal1 == "true"
gemInstance.enableGlobal2 = child.attrib.enableGlobal2 == "true"
Expand Down Expand Up @@ -393,6 +395,8 @@ function SkillsTabClass:Load(xml, fileName)
if node.elem == "SkillSet" then
local skillSet = self:NewSkillSet(tonumber(node.attrib.id))
skillSet.title = node.attrib.title
skillSet.levelMin = tonumber(node.attrib.levelMin)
skillSet.levelMax = tonumber(node.attrib.levelMax)
t_insert(self.skillSetOrderList, skillSet.id)
for _, subNode in ipairs(node) do
self:LoadSkill(subNode, skillSet.id)
Expand All @@ -414,7 +418,7 @@ function SkillsTabClass:Save(xml)
}
for _, skillSetId in ipairs(self.skillSetOrderList) do
local skillSet = self.skillSets[skillSetId]
local child = { elem = "SkillSet", attrib = { id = tostring(skillSetId), title = skillSet.title } }
local child = { elem = "SkillSet", attrib = { id = tostring(skillSetId), title = skillSet.title, levelMin = skillSet.levelMin and tostring(skillSet.levelMin) or nil, levelMax = skillSet.levelMax and tostring(skillSet.levelMax) or nil } }
t_insert(xml, child)

for _, socketGroup in ipairs(skillSet.socketGroupList) do
Expand Down Expand Up @@ -454,6 +458,7 @@ function SkillsTabClass:Save(xml)
skillMinionItemSetCalcs = gemInstance.skillMinionItemSetCalcs and tostring(gemInstance.skillMinionItemSetCalcs),
skillMinionSkill = gemInstance.skillMinionSkill and tostring(gemInstance.skillMinionSkill),
skillMinionSkillCalcs = gemInstance.skillMinionSkillCalcs and tostring(gemInstance.skillMinionSkillCalcs),
note = (gemInstance.note and gemInstance.note ~= "") and gemInstance.note or nil,
} }
if gemInstance.statSet then
for grantedEffect, index in pairs(gemInstance.statSet) do
Expand Down Expand Up @@ -1261,12 +1266,53 @@ end

-- Opens the skill set manager
function SkillsTabClass:OpenSkillSetManagePopup()
main:OpenPopup(370, 290, "Manage Skill Sets", {
new("SkillSetListControl", nil, {0, 50, 350, 200}, self),
new("ButtonControl", nil, {0, 260, 90, 20}, "Done", function()
main:ClosePopup()
end),
})
local controls = { }
controls.setList = new("SkillSetListControl", nil, {0, 50, 350, 200}, self)

-- Level bracket inputs for the .build (PoE2 BuildPlanner) export.
local function clampLvl(buf)
local n = tonumber(buf)
if not n then return nil end
n = math.floor(n)
if n < 0 then n = 0 end
if n > 100 then n = 100 end
return n
end
local function selectedSet()
return self.skillSets[controls.setList.selValue]
end
controls.lvlLabel = new("LabelControl", nil, {-95, 260, 0, 16}, "^7Lvl range:")
controls.lvlMin = new("EditControl", nil, {-30, 260, 60, 20}, nil, nil, "%D", 3, function(buf)
local set = selectedSet()
if set then
set.levelMin = clampLvl(buf)
self.build.modFlag = true
end
end)
controls.lvlMin.tooltipText = "Lowest character level this skill set applies to in the exported .build (1-100). Leave blank to auto-split across skill sets."
controls.lvlDash = new("LabelControl", nil, {30, 260, 0, 16}, "^7-")
controls.lvlMax = new("EditControl", nil, {80, 260, 60, 20}, nil, nil, "%D", 3, function(buf)
local set = selectedSet()
if set then
set.levelMax = clampLvl(buf)
self.build.modFlag = true
end
end)
controls.lvlMax.tooltipText = "Highest character level this skill set applies to in the exported .build (1-100). Leave blank to auto-split across skill sets."
local function refreshLvl()
local set = selectedSet()
controls.lvlMin:SetText(set and set.levelMin and tostring(set.levelMin) or "")
controls.lvlMax:SetText(set and set.levelMax and tostring(set.levelMax) or "")
end
controls.setList.OnSelClick = function(listSelf, index, value, doubleClick)
refreshLvl()
end
refreshLvl()

controls.close = new("ButtonControl", nil, {155, 260, 50, 20}, "Done", function()
main:ClosePopup()
end)
main:OpenPopup(370, 290, "Manage Skill Sets", controls)
end

-- Creates a new skill set
Expand Down
Loading