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
4 changes: 3 additions & 1 deletion help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,11 @@ Loadouts can be selected from the dropdown in the top middle of the screen. Sele

3) If there is only one set for a set type (except passive tree), e.g. "Default" config set, it will be assigned to all existing loadouts.

The "New Loadout" option allows the user to create all four sets from a single popup for convenience.
The "Manage Loadouts... (ctrl-l)" option opens a popup that lists your loadouts and lets you manage them as a whole: create (New), rename, copy and delete a loadout (its tree, item, skill and config sets together), and drag to reorder loadouts without moving each set individually. Deleting a loadout will not delete any items used by it, and a set shared between loadouts (or the only set of its type) is left in place.
The "Sync" option is a backup option to force the UI to update in case the user has changed this data behind the scenes.

Note that a loadout is matched by the plain set name, so a loadout whose tree is on an older tree version (shown with a "[version]" prefix, e.g. "[3.28 (alternate)]") still groups with its plainly-named item, skill and config sets.


---[Party Tab]

Expand Down
1 change: 1 addition & 0 deletions manifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
<File name="Classes/ItemsTab.lua" part="program" sha1="9fef230581257b4a8c3a4fcb24bb3bd932585bca" />
<File name="Classes/LabelControl.lua" part="program" sha1="b6c56903b054e14c302a5e4e60ed8e91764bc6a0" />
<File name="Classes/ListControl.lua" part="program" sha1="ba5079fe6a1a38bc8c1cf758d42a62a4a7209e84" />
<File name="Classes/ManageLoadoutsListControl.lua" part="program" sha1="f3d8d378a64ecfc1f2e6c63f688fbadacf2ffdd9" />
<File name="Classes/MinionListControl.lua" part="program" sha1="b9324d7c16d20ac4d7508ecd151c2e9838fdb98c" />
<File name="Classes/MinionSearchListControl.lua" part="program" sha1="374e51bfb5c15eabbf1e28b7493aaff67cb1efc6" />
<File name="Classes/ModDB.lua" part="program" sha1="14d704ed402c24b65e96ba098e6996fc1e3601da" />
Expand Down
117 changes: 117 additions & 0 deletions spec/System/TestManageLoadouts_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
describe("ManageLoadouts", function()
before_each(function()
newBuild()
end)

-- pick a tree version that is not the latest, to exercise the version-prefix handling
local function nonLatestVersion()
for _, version in ipairs(treeVersionList) do
if version ~= latestTreeVersion then
return version
end
end
end

local function titleExists(sets, orderList, title)
for _, id in ipairs(orderList) do
if (sets[id].title or "Default") == title then
return true
end
end
return false
end

it("strips known tree-version prefixes but leaves other bracketed names alone", function()
local display = treeVersions[nonLatestVersion()].display
assert.are.equal("Lvl 1", build:StripTreeVersionPrefix("["..display.."] Lvl 1"))
-- unknown bracketed text is not a version, so it must be preserved
assert.are.equal("[Boss] Setup", build:StripTreeVersionPrefix("[Boss] Setup"))
assert.are.equal("Plain Name", build:StripTreeVersionPrefix("Plain Name"))
end)

it("recognises a loadout even when its tree is on a non-latest version", function()
build:CreateLoadout("Leveling")
-- move the new tree to an older version; the item/skill/config sets keep the plain name
build.treeTab.specList[#build.treeTab.specList].treeVersion = nonLatestVersion()

local loadouts = build:GetLoadouts()
local found = false
for _, loadout in ipairs(loadouts) do
if loadout.name == "Leveling" then
found = true
end
end
assert.is_true(found)
end)

it("deletes every set belonging to a loadout", function()
local specCount = #build.treeTab.specList
local itemCount = #build.itemsTab.itemSetOrderList
local skillCount = #build.skillsTab.skillSetOrderList
local configCount = #build.configTab.configSetOrderList

build:CreateLoadout("Boss")
assert.are.equal(specCount + 1, #build.treeTab.specList)
assert.are.equal(itemCount + 1, #build.itemsTab.itemSetOrderList)

local target
for _, loadout in ipairs(build:GetLoadouts()) do
if loadout.name == "Boss" then
target = loadout
end
end
assert.is_not_nil(target)

build:DeleteLoadout(target)

assert.are.equal(specCount, #build.treeTab.specList)
assert.are.equal(itemCount, #build.itemsTab.itemSetOrderList)
assert.are.equal(skillCount, #build.skillsTab.skillSetOrderList)
assert.are.equal(configCount, #build.configTab.configSetOrderList)

assert.is_false(titleExists(build.itemsTab.itemSets, build.itemsTab.itemSetOrderList, "Boss"))
assert.is_false(titleExists(build.skillsTab.skillSets, build.skillsTab.skillSetOrderList, "Boss"))
assert.is_false(titleExists(build.configTab.configSets, build.configTab.configSetOrderList, "Boss"))
end)

it("reorders every set of a loadout together", function()
build:CreateLoadout("A")
build:CreateLoadout("B")

local loadouts = build:GetLoadouts()
-- find A and B and build a new order with their positions swapped
local indexA, indexB
for i, loadout in ipairs(loadouts) do
if loadout.name == "A" then indexA = i end
if loadout.name == "B" then indexB = i end
end
assert.is_not_nil(indexA)
assert.is_not_nil(indexB)

loadouts[indexA], loadouts[indexB] = loadouts[indexB], loadouts[indexA]
build:ApplyLoadoutOrder(loadouts)

-- the trees and the id-based order lists must all reflect the new order
local specTitles = { }
for _, spec in ipairs(build.treeTab.specList) do
table.insert(specTitles, spec.title or "Default")
end
local posA, posB
for i, title in ipairs(specTitles) do
if title == "A" then posA = i end
if title == "B" then posB = i end
end
assert.is_true(posB < posA)

local itemTitles = { }
for _, id in ipairs(build.itemsTab.itemSetOrderList) do
table.insert(itemTitles, build.itemsTab.itemSets[id].title or "Default")
end
local itemPosA, itemPosB
for i, title in ipairs(itemTitles) do
if title == "A" then itemPosA = i end
if title == "B" then itemPosB = i end
end
assert.is_true(itemPosB < itemPosA)
end)
end)
139 changes: 139 additions & 0 deletions src/Classes/ManageLoadoutsListControl.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
-- Path of Building
--
-- Class: Manage Loadouts List
-- List control for managing whole loadouts (a passive tree + item/skill/config sets sharing a name).
--
local ipairs = ipairs

local ManageLoadoutsListClass = newClass("ManageLoadoutsListControl", "ListControl", function(self, anchor, rect, build)
self.ListControl(anchor, rect, 16, "VERTICAL", true, { })
self.build = build
self:BuildList()

self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function()
self:CopyLoadout(self.selValue)
end)
self.controls.copy.enabled = function()
return self.selValue ~= nil
end
self.controls.delete = new("ButtonControl", {"LEFT",self.controls.copy,"RIGHT"}, {4, 0, 60, 18}, "Delete", function()
self:OnSelDelete(self.selIndex, self.selValue)
end)
self.controls.delete.enabled = function()
return self.selValue ~= nil and #self.list > 1
end
self.controls.rename = new("ButtonControl", {"BOTTOMRIGHT",self,"TOP"}, {-2, -4, 60, 18}, "Rename", function()
self:RenameLoadout(self.selValue)
end)
self.controls.rename.enabled = function()
return self.selValue ~= nil
end
self.controls.new = new("ButtonControl", {"RIGHT",self.controls.rename,"LEFT"}, {-4, 0, 60, 18}, "New", function()
self.build:OpenNewLoadoutPopup(function()
self:BuildList()
end)
end)
end)

-- Rebuild the list of loadout descriptors, preserving the current selection by tree spec identity.
function ManageLoadoutsListClass:BuildList()
local prevSpec = self.selValue and self.selValue.spec
self.list = self.build:GetLoadouts()
self.selIndex = nil
self.selValue = nil
if prevSpec then
for index, loadout in ipairs(self.list) do
if loadout.spec == prevSpec then
self.selIndex = index
self.selValue = loadout
break
end
end
end
end

function ManageLoadoutsListClass:GetRowValue(column, index, loadout)
if column == 1 then
local spec = loadout.spec
local used = spec:CountAllocNodes()
local className = spec.curAscendClassName ~= "None" and spec.curAscendClassName or spec.curClassName
local isCurrent = self.build.treeTab.specList[self.build.treeTab.activeSpec] == spec
return (spec.treeVersion ~= latestTreeVersion and ("["..treeVersions[spec.treeVersion].display.."] ") or "")
.. loadout.name
.. " (" .. className .. ", " .. used .. " points)"
.. (isCurrent and " ^9(Current)" or "")
end
end

function ManageLoadoutsListClass:OnSelClick(index, loadout, doubleClick)
if doubleClick then
self.build:SetActiveLoadout(loadout)
self:BuildList()
end
end

function ManageLoadoutsListClass:OnOrderChange()
self.build:ApplyLoadoutOrder(self.list)
self:BuildList()
end

function ManageLoadoutsListClass:OnSelDelete(index, loadout)
if not loadout or #self.list <= 1 then
return
end
main:OpenConfirmPopup("Delete Loadout",
"Are you sure you want to delete the '"..loadout.name.."' loadout?\n"
.. "This will delete its passive tree, item set, skill set and config set.\n"
.. "This will not delete any items used by the set.", "Delete", function()
self.build:DeleteLoadout(loadout)
self:BuildList()
end)
end

function ManageLoadoutsListClass:RenameLoadout(loadout)
if not loadout then
return
end
local controls = { }
controls.label = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:")
controls.edit = new("EditControl", nil, {0, 40, 350, 20}, loadout.name, nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, {-45, 70, 80, 20}, "Save", function()
self.build:RenameLoadout(loadout, controls.edit.buf)
self:BuildList()
main:ClosePopup()
end)
controls.save.enabled = false
controls.cancel = new("ButtonControl", nil, {45, 70, 80, 20}, "Cancel", function()
main:ClosePopup()
end)
main:OpenPopup(370, 100, "Rename Loadout", controls, "save", "edit", "cancel")
end

function ManageLoadoutsListClass:CopyLoadout(loadout)
if not loadout then
return
end
local controls = { }
controls.label = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for the copied loadout:")
controls.edit = new("EditControl", nil, {0, 40, 350, 20}, loadout.name, nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, {-45, 70, 80, 20}, "Save", function()
self.build:CopyLoadout(loadout, controls.edit.buf)
self:BuildList()
main:ClosePopup()
end)
controls.save.enabled = false
controls.cancel = new("ButtonControl", nil, {45, 70, 80, 20}, "Cancel", function()
main:ClosePopup()
end)
main:OpenPopup(370, 100, "Copy Loadout", controls, "save", "edit", "cancel")
end

function ManageLoadoutsListClass:OnSelKeyDown(index, loadout, key)
if key == "F2" then
self:RenameLoadout(loadout)
end
end
Loading