Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e6e88f5
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 15, 2026
829d956
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 16, 2026
877ee6b
[ISSUE-9451] Loadout Managment
FenikSRT4 Mar 17, 2026
d0236a2
[ISSUE-9541] Loadout Management
FenikSRT4 Mar 18, 2026
ff4ec93
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 19, 2026
96b1a76
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 19, 2026
12ea119
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 29, 2026
73c2985
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 29, 2026
50cfedd
Merge branch 'dev' into issue-9451-loadout-management
FenikSRT4 Mar 29, 2026
7f8b8ed
[ISSUE-9451] Loadout Management
FenikSRT4 Mar 30, 2026
03964c6
[ISSUE-8451] Loadout Managment
FenikSRT4 Apr 1, 2026
c0b199f
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
7845daf
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
d72fb79
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
565fb82
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
e1e48d9
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
1d80e0a
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 4, 2026
01df330
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 5, 2026
423e503
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 9, 2026
2b373f4
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 9, 2026
a243930
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 10, 2026
7b13b85
Merge branch 'dev' into issue-9451-loadout-management
FenikSRT4 Apr 10, 2026
ab92f8c
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 10, 2026
fa4ad22
[ISSUE-9451] Loadout Management
FenikSRT4 Apr 14, 2026
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
645 changes: 645 additions & 0 deletions spec/System/TestLoadouts_spec.lua

Large diffs are not rendered by default.

231 changes: 231 additions & 0 deletions src/Classes/BuildSetListControl.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
-- Path of Building
--
-- Class: Build Set List
-- Build set list control.
--
local t_insert = table.insert
local t_remove = table.remove

local BuildSetListClass = newClass("BuildSetListControl", "ListControl", function(self, anchor, rect, buildMode)
self.ListControl(anchor, rect, 16, "VERTICAL", true, buildMode.treeTab.specList)
self.buildMode = buildMode
self.buildSetService = new("BuildSetService", buildMode)
self.controls.new = new("ButtonControl", { "BOTTOMLEFT", self, "TOP" }, { -190, -4, 60, 18 }, "New",
function()
self:NewLoadout()
end)
self.controls.rename = new("ButtonControl", { "LEFT", self.controls.new, "RIGHT" }, { 5, 0, 60, 18 }, "Rename",
function()
self:RenameLoadout(self.selValue)
end)
self.controls.rename.enabled = function()
return self.selValue ~= nil
end
self.controls.copy = new("ButtonControl", { "LEFT", self.controls.rename, "RIGHT" }, { 5, 0, 60, 18 }, "Copy",
function()
local loadoutNameToCopy = self.selValue.title or "Default"
self:CopyLoadout(loadoutNameToCopy)
end)
self.controls.copy.enabled = function()
return self.selValue ~= nil
end
self.controls.delete = new("ButtonControl", { "LEFT", self.controls.copy, "RIGHT" }, { 5, 0, 60, 18 }, "Delete",
function()
self:DeleteLoadout(self.selIndex, self.selValue)
end)
self.controls.delete.enabled = function()
return self.selValue ~= nil and #self.list > 1
end
self.controls.custom = new("ButtonControl", { "LEFT", self.controls.delete, "RIGHT" }, { 5, 0, 120, 18 },
"New/Copy Custom",
function()
if self.selValue == nil then
self:CustomLoadout({
specId = 0,
itemSetId = 0,
skillSetId = 0,
configSetId = 0,
})
else
local loadoutNameToCopy = self.selValue.title or "Default"
local build = buildMode:GetLoadoutByName(loadoutNameToCopy)
self:CustomLoadout(build)
end
end)
end)

function BuildSetListClass:RenameLoadout(spec)
local controls = {}
local specName = spec.title or "Default"
controls.label = new("LabelControl", nil, { 0, 20, 0, 16 }, "^7Enter name for this loadout:")
controls.edit = new("EditControl", nil, { 0, 40, 350, 20 }, specName, nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, { -45, 70, 80, 20 }, "Save", function()
local newTitle = controls.edit.buf
self.buildSetService:RenameLoadout(specName, newTitle)
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, specName and "Rename Loadout" or "Set Name", controls, "save", "edit", "cancel")
end

function BuildSetListClass:CopyLoadout(loadoutName)
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 }, loadoutName, nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, { -45, 70, 80, 20 }, "Save", function()
self.buildSetService:CopyLoadout(loadoutName, controls.edit.buf)
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, loadoutName and "Rename" or "Set Name", controls, "save", "edit", "cancel")
end

function BuildSetListClass:CustomLoadout(build)
local function getList(setList, orderList, activeId)
local newSetList = { "^7New" }
local activeIndex = 1
for index, setId in ipairs(orderList) do
local set = setList[setId]
t_insert(newSetList, set.title or "Default")
if setId == activeId then
activeIndex = index + 1
end
end
return newSetList, activeIndex
end
local controls = {}
local buildName = build.specId > 0 and self.buildMode.treeTab.specList[build.specId].title or "New Loadout"
controls.label = new("LabelControl", nil, { 0, 20, 0, 16 }, "^7Enter name for this loadout:")
controls.edit = new("EditControl", nil, { 0, 40, 350, 20 }, buildName, nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)

local treeList = self.buildMode.treeTab:GetSpecList()
t_insert(treeList, 1, "^7New")
controls.treeDropDown = new("DropDownControl", nil, { 0, 90, 350, 20 }, treeList, function(index)

end)
controls.treeDropDown:SetSel(build.specId + 1)
controls.treeLabel = new("LabelControl", { "BOTTOMLEFT", controls.treeDropDown, "TOPLEFT" }, { 4, -4, 0, 16 },
"^7Copy from Tree:")

local skillList, activeSkillIndex = getList(self.buildMode.skillsTab.skillSets,
self.buildMode.skillsTab.skillSetOrderList, build.skillSetId)
controls.skillDropDown = new("DropDownControl", nil, { 0, 140, 350, 20 }, skillList, function(index)

end)
controls.skillDropDown:SetSel(activeSkillIndex)
controls.skillLabel = new("LabelControl", { "BOTTOMLEFT", controls.skillDropDown, "TOPLEFT" }, { 4, -4, 0, 16 },
"^7Copy from Skill Set:")

local itemList, activeItemIndex = getList(self.buildMode.itemsTab.itemSets, self.buildMode.itemsTab.itemSetOrderList,
build.itemSetId)
controls.itemDropDown = new("DropDownControl", nil, { 0, 190, 350, 20 }, itemList, function(index)

end)
controls.itemDropDown:SetSel(activeItemIndex)
controls.itemLabel = new("LabelControl", { "BOTTOMLEFT", controls.itemDropDown, "TOPLEFT" }, { 4, -4, 0, 16 },
"^7Copy from Item Set:")

local configList, activeConfigIndex = getList(self.buildMode.configTab.configSets,
self.buildMode.configTab.configSetOrderList, build.configSetId)
controls.configDropDown = new("DropDownControl", nil, { 0, 240, 350, 20 }, configList, function(index)

end)
controls.configDropDown:SetSel(activeConfigIndex)
controls.configLabel = new("LabelControl", { "BOTTOMLEFT", controls.configDropDown, "TOPLEFT" }, { 4, -4, 0, 16 },
"^7Copy from Config Set:")

controls.save = new("ButtonControl", nil, { -45, 270, 80, 20 }, "Save", function()
local treeIndex = controls.treeDropDown.selIndex
local itemIndex = controls.itemDropDown.selIndex
local skillIndex = controls.skillDropDown.selIndex
local configIndex = controls.configDropDown.selIndex

local newSpecId = treeIndex == 1 and -1 or (treeIndex > 1 and treeIndex - 1)
local newItemSetId = itemIndex == 1 and -1 or
(itemIndex > 1 and self.buildMode.itemsTab.itemSetOrderList[itemIndex - 1] or 0)
local newSkillSetId = skillIndex == 1 and -1 or
(skillIndex > 1 and self.buildMode.skillsTab.skillSetOrderList[skillIndex - 1] or 0)
local newConfigSetId = configIndex == 1 and -1 or
(configIndex > 1 and self.buildMode.configTab.configSetOrderList[configIndex - 1] or 0)
print("Before CustomLoadout: ", newSpecId, newItemSetId, newSkillSetId, newConfigSetId)
self.buildSetService:CustomLoadout(newSpecId, newItemSetId, newSkillSetId, newConfigSetId,
controls.edit.buf)
print("After CustomLoadout: ", newSpecId, newItemSetId, newSkillSetId, newConfigSetId)
main:ClosePopup()
print("After ClosePopup: ", newSpecId, newItemSetId, newSkillSetId, newConfigSetId)
end)
controls.save.enabled = false
controls.cancel = new("ButtonControl", nil, { 45, 270, 80, 20 }, "Cancel", function()
main:ClosePopup()
end)
main:OpenPopup(370, 300, "Create Custom Loadout", controls, "save", "edit", "cancel")
end

function BuildSetListClass:NewLoadout()
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 }, "New Loadout", nil, nil, 100, function(buf)
controls.save.enabled = buf:match("%S")
end)
controls.save = new("ButtonControl", nil, { -45, 70, 80, 20 }, "Save", function()
self.buildSetService:NewLoadout(controls.edit.buf)
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, "Set Name", controls, "save", "edit", "cancel")
end

function BuildSetListClass:GetRowValue(column, index, spec)
if column == 1 then
local used = spec:CountAllocNodes()
return (spec.treeVersion ~= latestTreeVersion and ("[" .. treeVersions[spec.treeVersion].display .. "] ") or "")
.. (spec.title or "Default")
..
" (" ..
(spec.curAscendClassName ~= "None" and spec.curAscendClassName or spec.curClassName) ..
", " .. used .. " points)"
.. (index == self.buildMode.treeTab.activeSpec and " ^9(Current)" or "")
end
end

function BuildSetListClass:OnSelClick(index, spec, doubleClick)
if doubleClick and index ~= self.buildMode.treeTab.activeSpec then
self.buildMode.controls.buildLoadouts:SetSel(index + 1)
end
end

function BuildSetListClass:DeleteLoadout(index, spec)
if #self.list > 1 then
main:OpenConfirmPopup("Delete Loadout", "Are you sure you want to delete '" .. (spec.title or "Default") .. "'?",
"Delete", function()
self.buildSetService:DeleteLoadout(index, self.list, spec)
self.selIndex = nil
self.selValue = nil
end)
end
end

function BuildSetListClass:OnSelKeyDown(index, spec, key)
if key == "F2" then
self:RenameLoadout(spec)
end
end

function BuildSetListClass:OnOrderChange(oldIndex, newIndex)
self.buildSetService:ReorderLoadout(oldIndex, newIndex)
end
43 changes: 43 additions & 0 deletions src/Classes/BuildSetService.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- Path of Building
--
-- Module: BuildSetService
-- Build set service for managing loadouts.

-- Utility functions
local m_max = math.max
local t_insert = table.insert


local BuildSetServiceClass = newClass("BuildSetService", function(self, buildMode)
self.buildMode = buildMode
end)

function BuildSetServiceClass:NewLoadout(name)
self.buildMode:NewLoadout(name)
self.buildMode:SyncLoadouts()
self.buildMode.controls.buildLoadouts:SetSel(1)
end

function BuildSetServiceClass:CopyLoadout(copyLoadoutName, newName)
self.buildMode:CopyLoadout(copyLoadoutName, newName)
end

function BuildSetServiceClass:RenameLoadout(oldName, newName)
self.buildMode:RenameLoadout(oldName, newName)
self.buildMode:SyncLoadouts()
end

function BuildSetServiceClass:DeleteLoadout(index, list, spec)
local nextLoadoutIndex = index == self.buildMode.treeTab.activeSpec and (index > 1 and index - 1 or index + 1) or
self.buildMode.treeTab.activeSpec
local nextLoadout = list[nextLoadoutIndex]
self.buildMode:DeleteLoadout(spec.title or "Default", nextLoadout.title or "Default")
end

function BuildSetServiceClass:CustomLoadout(specId, itemSetId, skillSetId, configSetId, name)
self.buildMode:CustomLoadout(specId, itemSetId, skillSetId, configSetId, name)
end

function BuildSetServiceClass:ReorderLoadout(oldIndex, newIndex)
self.buildMode:ReorderLoadout(oldIndex, newIndex)
end
17 changes: 3 additions & 14 deletions src/Classes/ConfigSetListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ local ConfigSetListClass = newClass("ConfigSetListControl", "ListControl", funct
self.ListControl(anchor, rect, 16, "VERTICAL", true, configTab.configSetOrderList)
self.configTab = configTab
self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function()
local configSet = configTab.configSets[self.selValue]
local newConfigSet = copyTable(configSet)
newConfigSet.id = 1
while configTab.configSets[newConfigSet.id] do
newConfigSet.id = newConfigSet.id + 1
end
configTab.configSets[newConfigSet.id] = newConfigSet
local newConfigSet = configTab:CopyConfigSet(self.selValue)
self:RenameSet(newConfigSet, true)
end)
self.controls.copy.enabled = function()
Expand Down Expand Up @@ -90,15 +84,10 @@ function ConfigSetListClass:OnSelDelete(index, configSetId)
local configSet = self.configTab.configSets[configSetId]
if #self.list > 1 then
main:OpenConfirmPopup("Delete Config Set", "Are you sure you want to delete '"..(configSet.title or "Default").."'?", "Delete", function()
t_remove(self.list, index)
self.configTab.configSets[configSetId] = nil
self.configTab:DeleteConfigSet(configSetId, index)

self.selIndex = nil
self.selValue = nil
if configSetId == self.configTab.activeConfigSetId then
self.configTab:SetActiveConfigSet(self.list[m_max(1, index - 1)])
end
self.configTab:AddUndoState()
self.configTab.build:SyncLoadouts()
end)
end
end
Expand Down
33 changes: 27 additions & 6 deletions src/Classes/ConfigTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
-- Configuration tab for the current build.
--
local t_insert = table.insert
local t_remove = table.remove
local t_maxn = table.maxn
local m_min = math.min
local m_max = math.max
local m_floor = math.floor
Expand Down Expand Up @@ -958,10 +960,7 @@ end
function ConfigTabClass:NewConfigSet(configSetId, title)
local configSet = { id = configSetId, title = title, input = { }, placeholder = { } }
if not configSetId then
configSet.id = 1
while self.configSets[configSet.id] do
configSet.id = configSet.id + 1
end
configSet.id = #self.configSets + 1
end
-- there are default values for input and placeholder that every new config set needs to have
for _, varData in ipairs(varList) do
Expand All @@ -977,8 +976,28 @@ function ConfigTabClass:NewConfigSet(configSetId, title)
return configSet
end

function ConfigTabClass:CopyConfigSet(configSetId, newConfigSetName)
local configSet = self.configSets[configSetId]
local newConfigSet = copyTable(configSet)
newConfigSet.id = #self.configSets + 1
newConfigSet.title = newConfigSetName or configSet.title .. " (Copy)"
t_insert(self.configSets, newConfigSet)
return newConfigSet
end

-- Deletes a config set
function ConfigTabClass:DeleteConfigSet(configSetId, orderListIndex)
t_remove(self.configSetOrderList, orderListIndex)
self.configSets[configSetId] = nil
if configSetId == self.activeConfigSetId then
self:SetActiveConfigSet(self.configSetOrderList[m_max(1, orderListIndex - 1)])
end
self:AddUndoState()
self.build:SyncLoadouts()
end

-- Changes the active config set
function ConfigTabClass:SetActiveConfigSet(configSetId, init)
function ConfigTabClass:SetActiveConfigSet(configSetId, init, deferSync)
-- Initialize config sets if needed
if not self.configSetOrderList[1] then
self.configSetOrderList[1] = 1
Expand All @@ -1002,5 +1021,7 @@ function ConfigTabClass:SetActiveConfigSet(configSetId, init)
self:BuildModList()
end
self.build.buildFlag = true
self.build:SyncLoadouts()
if not deferSync then
self.build:SyncLoadouts()
end
end
7 changes: 1 addition & 6 deletions src/Classes/ItemSetListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ local ItemSetListClass = newClass("ItemSetListControl", "ListControl", function(
self.ListControl(anchor, rect, 16, "VERTICAL", true, itemsTab.itemSetOrderList)
self.itemsTab = itemsTab
self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function()
local newSet = copyTable(itemsTab.itemSets[self.selValue])
newSet.id = 1
while itemsTab.itemSets[newSet.id] do
newSet.id = newSet.id + 1
end
itemsTab.itemSets[newSet.id] = newSet
local newSet = itemsTab:CopyItemSet(self.selValue)
self:RenameSet(newSet, true)
end)
self.controls.copy.enabled = function()
Expand Down
Loading
Loading