Skip to content

Commit 35dda6c

Browse files
committed
Add Manage Loadouts view and version-prefix-tolerant matching
1 parent 92bc0df commit 35dda6c

5 files changed

Lines changed: 632 additions & 45 deletions

File tree

help.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,11 @@ Loadouts can be selected from the dropdown in the top middle of the screen. Sele
133133

134134
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.
135135

136-
The "New Loadout" option allows the user to create all four sets from a single popup for convenience.
136+
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.
137137
The "Sync" option is a backup option to force the UI to update in case the user has changed this data behind the scenes.
138138

139+
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.
140+
139141

140142
---[Party Tab]
141143

manifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
<File name="Classes/ItemsTab.lua" part="program" sha1="9fef230581257b4a8c3a4fcb24bb3bd932585bca" />
150150
<File name="Classes/LabelControl.lua" part="program" sha1="b6c56903b054e14c302a5e4e60ed8e91764bc6a0" />
151151
<File name="Classes/ListControl.lua" part="program" sha1="ba5079fe6a1a38bc8c1cf758d42a62a4a7209e84" />
152+
<File name="Classes/ManageLoadoutsListControl.lua" part="program" sha1="f3d8d378a64ecfc1f2e6c63f688fbadacf2ffdd9" />
152153
<File name="Classes/MinionListControl.lua" part="program" sha1="b9324d7c16d20ac4d7508ecd151c2e9838fdb98c" />
153154
<File name="Classes/MinionSearchListControl.lua" part="program" sha1="374e51bfb5c15eabbf1e28b7493aaff67cb1efc6" />
154155
<File name="Classes/ModDB.lua" part="program" sha1="14d704ed402c24b65e96ba098e6996fc1e3601da" />
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
describe("ManageLoadouts", function()
2+
before_each(function()
3+
newBuild()
4+
end)
5+
6+
-- pick a tree version that is not the latest, to exercise the version-prefix handling
7+
local function nonLatestVersion()
8+
for _, version in ipairs(treeVersionList) do
9+
if version ~= latestTreeVersion then
10+
return version
11+
end
12+
end
13+
end
14+
15+
local function titleExists(sets, orderList, title)
16+
for _, id in ipairs(orderList) do
17+
if (sets[id].title or "Default") == title then
18+
return true
19+
end
20+
end
21+
return false
22+
end
23+
24+
it("strips known tree-version prefixes but leaves other bracketed names alone", function()
25+
local display = treeVersions[nonLatestVersion()].display
26+
assert.are.equal("Lvl 1", build:StripTreeVersionPrefix("["..display.."] Lvl 1"))
27+
-- unknown bracketed text is not a version, so it must be preserved
28+
assert.are.equal("[Boss] Setup", build:StripTreeVersionPrefix("[Boss] Setup"))
29+
assert.are.equal("Plain Name", build:StripTreeVersionPrefix("Plain Name"))
30+
end)
31+
32+
it("recognises a loadout even when its tree is on a non-latest version", function()
33+
build:CreateLoadout("Leveling")
34+
-- move the new tree to an older version; the item/skill/config sets keep the plain name
35+
build.treeTab.specList[#build.treeTab.specList].treeVersion = nonLatestVersion()
36+
37+
local loadouts = build:GetLoadouts()
38+
local found = false
39+
for _, loadout in ipairs(loadouts) do
40+
if loadout.name == "Leveling" then
41+
found = true
42+
end
43+
end
44+
assert.is_true(found)
45+
end)
46+
47+
it("deletes every set belonging to a loadout", function()
48+
local specCount = #build.treeTab.specList
49+
local itemCount = #build.itemsTab.itemSetOrderList
50+
local skillCount = #build.skillsTab.skillSetOrderList
51+
local configCount = #build.configTab.configSetOrderList
52+
53+
build:CreateLoadout("Boss")
54+
assert.are.equal(specCount + 1, #build.treeTab.specList)
55+
assert.are.equal(itemCount + 1, #build.itemsTab.itemSetOrderList)
56+
57+
local target
58+
for _, loadout in ipairs(build:GetLoadouts()) do
59+
if loadout.name == "Boss" then
60+
target = loadout
61+
end
62+
end
63+
assert.is_not_nil(target)
64+
65+
build:DeleteLoadout(target)
66+
67+
assert.are.equal(specCount, #build.treeTab.specList)
68+
assert.are.equal(itemCount, #build.itemsTab.itemSetOrderList)
69+
assert.are.equal(skillCount, #build.skillsTab.skillSetOrderList)
70+
assert.are.equal(configCount, #build.configTab.configSetOrderList)
71+
72+
assert.is_false(titleExists(build.itemsTab.itemSets, build.itemsTab.itemSetOrderList, "Boss"))
73+
assert.is_false(titleExists(build.skillsTab.skillSets, build.skillsTab.skillSetOrderList, "Boss"))
74+
assert.is_false(titleExists(build.configTab.configSets, build.configTab.configSetOrderList, "Boss"))
75+
end)
76+
77+
it("reorders every set of a loadout together", function()
78+
build:CreateLoadout("A")
79+
build:CreateLoadout("B")
80+
81+
local loadouts = build:GetLoadouts()
82+
-- find A and B and build a new order with their positions swapped
83+
local indexA, indexB
84+
for i, loadout in ipairs(loadouts) do
85+
if loadout.name == "A" then indexA = i end
86+
if loadout.name == "B" then indexB = i end
87+
end
88+
assert.is_not_nil(indexA)
89+
assert.is_not_nil(indexB)
90+
91+
loadouts[indexA], loadouts[indexB] = loadouts[indexB], loadouts[indexA]
92+
build:ApplyLoadoutOrder(loadouts)
93+
94+
-- the trees and the id-based order lists must all reflect the new order
95+
local specTitles = { }
96+
for _, spec in ipairs(build.treeTab.specList) do
97+
table.insert(specTitles, spec.title or "Default")
98+
end
99+
local posA, posB
100+
for i, title in ipairs(specTitles) do
101+
if title == "A" then posA = i end
102+
if title == "B" then posB = i end
103+
end
104+
assert.is_true(posB < posA)
105+
106+
local itemTitles = { }
107+
for _, id in ipairs(build.itemsTab.itemSetOrderList) do
108+
table.insert(itemTitles, build.itemsTab.itemSets[id].title or "Default")
109+
end
110+
local itemPosA, itemPosB
111+
for i, title in ipairs(itemTitles) do
112+
if title == "A" then itemPosA = i end
113+
if title == "B" then itemPosB = i end
114+
end
115+
assert.is_true(itemPosB < itemPosA)
116+
end)
117+
end)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
-- Path of Building
2+
--
3+
-- Class: Manage Loadouts List
4+
-- List control for managing whole loadouts (a passive tree + item/skill/config sets sharing a name).
5+
--
6+
local ipairs = ipairs
7+
8+
local ManageLoadoutsListClass = newClass("ManageLoadoutsListControl", "ListControl", function(self, anchor, rect, build)
9+
self.ListControl(anchor, rect, 16, "VERTICAL", true, { })
10+
self.build = build
11+
self:BuildList()
12+
13+
self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function()
14+
self:CopyLoadout(self.selValue)
15+
end)
16+
self.controls.copy.enabled = function()
17+
return self.selValue ~= nil
18+
end
19+
self.controls.delete = new("ButtonControl", {"LEFT",self.controls.copy,"RIGHT"}, {4, 0, 60, 18}, "Delete", function()
20+
self:OnSelDelete(self.selIndex, self.selValue)
21+
end)
22+
self.controls.delete.enabled = function()
23+
return self.selValue ~= nil and #self.list > 1
24+
end
25+
self.controls.rename = new("ButtonControl", {"BOTTOMRIGHT",self,"TOP"}, {-2, -4, 60, 18}, "Rename", function()
26+
self:RenameLoadout(self.selValue)
27+
end)
28+
self.controls.rename.enabled = function()
29+
return self.selValue ~= nil
30+
end
31+
self.controls.new = new("ButtonControl", {"RIGHT",self.controls.rename,"LEFT"}, {-4, 0, 60, 18}, "New", function()
32+
self.build:OpenNewLoadoutPopup(function()
33+
self:BuildList()
34+
end)
35+
end)
36+
end)
37+
38+
-- Rebuild the list of loadout descriptors, preserving the current selection by tree spec identity.
39+
function ManageLoadoutsListClass:BuildList()
40+
local prevSpec = self.selValue and self.selValue.spec
41+
self.list = self.build:GetLoadouts()
42+
self.selIndex = nil
43+
self.selValue = nil
44+
if prevSpec then
45+
for index, loadout in ipairs(self.list) do
46+
if loadout.spec == prevSpec then
47+
self.selIndex = index
48+
self.selValue = loadout
49+
break
50+
end
51+
end
52+
end
53+
end
54+
55+
function ManageLoadoutsListClass:GetRowValue(column, index, loadout)
56+
if column == 1 then
57+
local spec = loadout.spec
58+
local used = spec:CountAllocNodes()
59+
local className = spec.curAscendClassName ~= "None" and spec.curAscendClassName or spec.curClassName
60+
local isCurrent = self.build.treeTab.specList[self.build.treeTab.activeSpec] == spec
61+
return (spec.treeVersion ~= latestTreeVersion and ("["..treeVersions[spec.treeVersion].display.."] ") or "")
62+
.. loadout.name
63+
.. " (" .. className .. ", " .. used .. " points)"
64+
.. (isCurrent and " ^9(Current)" or "")
65+
end
66+
end
67+
68+
function ManageLoadoutsListClass:OnSelClick(index, loadout, doubleClick)
69+
if doubleClick then
70+
self.build:SetActiveLoadout(loadout)
71+
self:BuildList()
72+
end
73+
end
74+
75+
function ManageLoadoutsListClass:OnOrderChange()
76+
self.build:ApplyLoadoutOrder(self.list)
77+
self:BuildList()
78+
end
79+
80+
function ManageLoadoutsListClass:OnSelDelete(index, loadout)
81+
if not loadout or #self.list <= 1 then
82+
return
83+
end
84+
main:OpenConfirmPopup("Delete Loadout",
85+
"Are you sure you want to delete the '"..loadout.name.."' loadout?\n"
86+
.. "This will delete its passive tree, item set, skill set and config set.\n"
87+
.. "This will not delete any items used by the set.", "Delete", function()
88+
self.build:DeleteLoadout(loadout)
89+
self:BuildList()
90+
end)
91+
end
92+
93+
function ManageLoadoutsListClass:RenameLoadout(loadout)
94+
if not loadout then
95+
return
96+
end
97+
local controls = { }
98+
controls.label = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:")
99+
controls.edit = new("EditControl", nil, {0, 40, 350, 20}, loadout.name, nil, nil, 100, function(buf)
100+
controls.save.enabled = buf:match("%S")
101+
end)
102+
controls.save = new("ButtonControl", nil, {-45, 70, 80, 20}, "Save", function()
103+
self.build:RenameLoadout(loadout, controls.edit.buf)
104+
self:BuildList()
105+
main:ClosePopup()
106+
end)
107+
controls.save.enabled = false
108+
controls.cancel = new("ButtonControl", nil, {45, 70, 80, 20}, "Cancel", function()
109+
main:ClosePopup()
110+
end)
111+
main:OpenPopup(370, 100, "Rename Loadout", controls, "save", "edit", "cancel")
112+
end
113+
114+
function ManageLoadoutsListClass:CopyLoadout(loadout)
115+
if not loadout then
116+
return
117+
end
118+
local controls = { }
119+
controls.label = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for the copied loadout:")
120+
controls.edit = new("EditControl", nil, {0, 40, 350, 20}, loadout.name, nil, nil, 100, function(buf)
121+
controls.save.enabled = buf:match("%S")
122+
end)
123+
controls.save = new("ButtonControl", nil, {-45, 70, 80, 20}, "Save", function()
124+
self.build:CopyLoadout(loadout, controls.edit.buf)
125+
self:BuildList()
126+
main:ClosePopup()
127+
end)
128+
controls.save.enabled = false
129+
controls.cancel = new("ButtonControl", nil, {45, 70, 80, 20}, "Cancel", function()
130+
main:ClosePopup()
131+
end)
132+
main:OpenPopup(370, 100, "Copy Loadout", controls, "save", "edit", "cancel")
133+
end
134+
135+
function ManageLoadoutsListClass:OnSelKeyDown(index, loadout, key)
136+
if key == "F2" then
137+
self:RenameLoadout(loadout)
138+
end
139+
end

0 commit comments

Comments
 (0)