From 2af727452f4954fa74afc02ed2459efc650ebda7 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:15:22 -0600 Subject: [PATCH 01/10] initial impl of Renaming, Copying, and Deleting Loadouts, will update for clean up and other comments --- src/Classes/ConfigSetListControl.lua | 27 +- src/Classes/ItemSetListControl.lua | 30 ++- src/Classes/PassiveSpecListControl.lua | 36 ++- src/Classes/SkillSetListControl.lua | 27 +- src/Modules/Build.lua | 342 ++++++++++++++++++++----- 5 files changed, 389 insertions(+), 73 deletions(-) diff --git a/src/Classes/ConfigSetListControl.lua b/src/Classes/ConfigSetListControl.lua index c38e809d76..d24f61c798 100644 --- a/src/Classes/ConfigSetListControl.lua +++ b/src/Classes/ConfigSetListControl.lua @@ -10,15 +10,19 @@ local m_max = math.max local ConfigSetListClass = newClass("ConfigSetListControl", "ListControl", function(self, anchor, rect, configTab) 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] + self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function(id) -- id is for Loadouts using copy.onClick() + local configSet = configTab.configSets[id or 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 - self:RenameSet(newConfigSet, true) + if not id then + self:RenameSet(newConfigSet, true) + else + return newConfigSet + end end) self.controls.copy.enabled = function() return self.selValue ~= nil @@ -103,6 +107,23 @@ function ConfigSetListClass:OnSelDelete(index, configSetId) end end +-- bypass confirmation popup, used by Loadouts +function ConfigSetListClass:DeleteById(index, configSetId, sync) + if #self.list > 1 then + t_remove(self.list, index) + self.configTab.configSets[configSetId] = nil + 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() + if sync then + self.configTab.build:SyncLoadouts() + end + end +end + function ConfigSetListClass:OnSelKeyDown(index, configSetId, key) if key == "F2" then self:RenameSet(self.configTab.configSets[configSetId]) diff --git a/src/Classes/ItemSetListControl.lua b/src/Classes/ItemSetListControl.lua index 04d87e8537..23cfcbde08 100644 --- a/src/Classes/ItemSetListControl.lua +++ b/src/Classes/ItemSetListControl.lua @@ -11,14 +11,18 @@ local s_format = string.format local ItemSetListClass = newClass("ItemSetListControl", "ListControl", function(self, anchor, rect, itemsTab) 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]) + self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function(id) -- id is for Loadouts using copy.onClick() + local newSet = copyTable(itemsTab.itemSets[id or self.selValue]) newSet.id = 1 while itemsTab.itemSets[newSet.id] do newSet.id = newSet.id + 1 end itemsTab.itemSets[newSet.id] = newSet - self:RenameSet(newSet, true) + if not id then + self:RenameSet(newSet, true) + else + return newSet + end end) self.controls.copy.enabled = function() return self.selValue ~= nil @@ -118,6 +122,7 @@ end function ItemSetListClass:OnSelDelete(index, itemSetId) local itemSet = self.itemsTab.itemSets[itemSetId] + --ConPrintf("OnSelDelete, itemSet = "..itemSet.title or "Default"..", index = "..index..", itemSetId = "..itemSetId) if #self.list > 1 then main:OpenConfirmPopup("Delete Item Set", "Are you sure you want to delete '"..(itemSet.title or "Default").."'?\nThis will not delete any items used by the set.", "Delete", function() t_remove(self.list, index) @@ -133,6 +138,25 @@ function ItemSetListClass:OnSelDelete(index, itemSetId) end end +-- bypass confirmation popup, used by Loadouts +function ItemSetListClass:DeleteById(index, itemSetId, sync) + local itemSet = self.itemsTab.itemSets[itemSetId] + --ConPrintf("DeleteById, itemSet = "..itemSet.title or "Default"..", index = "..index..", itemSetId = "..itemSetId) + if #self.list > 1 then + t_remove(self.list, index) + self.itemsTab.itemSets[itemSetId] = nil + self.selIndex = nil + self.selValue = nil + if itemSetId == self.itemsTab.activeItemSetId then + self.itemsTab:SetActiveItemSet(self.list[m_max(1, index - 1)]) + end + self.itemsTab:AddUndoState() + if sync then + self.itemsTab.build:SyncLoadouts() + end + end +end + function ItemSetListClass:OnSelKeyDown(index, itemSetId, key) local itemSet = self.itemsTab.itemSets[itemSetId] if key == "F2" then diff --git a/src/Classes/PassiveSpecListControl.lua b/src/Classes/PassiveSpecListControl.lua index f227764d8f..1b1f340297 100644 --- a/src/Classes/PassiveSpecListControl.lua +++ b/src/Classes/PassiveSpecListControl.lua @@ -10,13 +10,18 @@ local m_max = math.max local PassiveSpecListClass = newClass("PassiveSpecListControl", "ListControl", function(self, anchor, rect, treeTab) self.ListControl(anchor, rect, 16, "VERTICAL", true, treeTab.specList) self.treeTab = treeTab - self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function() - local newSpec = new("PassiveSpec", treeTab.build, self.selValue.treeVersion) - newSpec.title = self.selValue.title - newSpec.jewels = copyTable(self.selValue.jewels) - newSpec:RestoreUndoState(self.selValue:CreateUndoState()) + self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function(spec) -- spec is for Loadouts using copy.onClick() + local selValue = spec or self.selValue + local newSpec = new("PassiveSpec", treeTab.build, selValue.treeVersion) + newSpec.title = selValue.title + newSpec.jewels = copyTable(selValue.jewels) + newSpec:RestoreUndoState(selValue:CreateUndoState()) newSpec:BuildClusterJewelGraphs() - self:RenameSpec(newSpec, "Copy Tree", true) + if not spec then + self:RenameSpec(newSpec, "Copy Tree", true) + else + return newSpec + end end) self.controls.copy.enabled = function() return self.selValue ~= nil @@ -110,6 +115,25 @@ function PassiveSpecListClass:OnSelDelete(index, spec) end end +-- bypass confirmation popup, used by Loadouts +function PassiveSpecListClass:DeleteByIndex(index, sync) + if #self.list > 1 then + t_remove(self.list, index) + self.selIndex = nil + self.selValue = nil + if index == self.treeTab.activeSpec then + self.treeTab:SetActiveSpec(1) + else + self.treeTab.activeSpec = isValueInArray(self.list, self.treeTab.build.spec) + end + self.treeTab.modFlag = true + self:UpdateItemsTabPassiveTreeDropdown() + if sync then + self.treeTab.build:SyncLoadouts() + end + end +end + function PassiveSpecListClass:OnSelKeyDown(index, spec, key) if key == "F2" then self:RenameSpec(spec, "Rename Tree") diff --git a/src/Classes/SkillSetListControl.lua b/src/Classes/SkillSetListControl.lua index c8208c476d..4430f39372 100644 --- a/src/Classes/SkillSetListControl.lua +++ b/src/Classes/SkillSetListControl.lua @@ -11,8 +11,8 @@ local s_format = string.format local SkillSetListClass = newClass("SkillSetListControl", "ListControl", function(self, anchor, rect, skillsTab) self.ListControl(anchor, rect, 16, "VERTICAL", true, skillsTab.skillSetOrderList) self.skillsTab = skillsTab - self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function() - local skillSet = skillsTab.skillSets[self.selValue] + self.controls.copy = new("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, {2, -4, 60, 18}, "Copy", function(id) -- id is for Loadouts using copy.onClick() + local skillSet = skillsTab.skillSets[id or self.selValue] local newSkillSet = copyTable(skillSet, true) newSkillSet.socketGroupList = { } for socketGroupIndex, socketGroup in pairs(skillSet.socketGroupList) do @@ -28,7 +28,11 @@ local SkillSetListClass = newClass("SkillSetListControl", "ListControl", functio newSkillSet.id = newSkillSet.id + 1 end skillsTab.skillSets[newSkillSet.id] = newSkillSet - self:RenameSet(newSkillSet, true) + if not id then + self:RenameSet(newSkillSet, true) + else + return newSkillSet + end end) self.controls.copy.enabled = function() return self.selValue ~= nil @@ -113,6 +117,23 @@ function SkillSetListClass:OnSelDelete(index, skillSetId) end end +-- bypass confirmation popup, used by Loadouts +function SkillSetListClass:DeleteById(index, skillSetId, sync) + if #self.list > 1 then + t_remove(self.list, index) + self.skillsTab.skillSets[skillSetId] = nil + self.selIndex = nil + self.selValue = nil + if skillSetId == self.skillsTab.activeSkillSetId then + self.skillsTab:SetActiveSkillSet(self.list[m_max(1, index - 1)]) + end + self.skillsTab:AddUndoState() + if sync then + self.skillsTab.build:SyncLoadouts() + end + end +end + function SkillSetListClass:OnSelKeyDown(index, skillSetId, key) if key == "F2" then self:RenameSet(self.skillsTab.skillSets[skillSetId]) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index b33f75c995..f7d2be5572 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -284,94 +284,314 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local initialSecondarySelection = (self.spec and self.spec.curSecondaryAscendClassId) or 0 self.controls.secondaryAscendDrop:SelByValue(initialSecondarySelection, "ascendClassId") self.controls.buildLoadouts = new("DropDownControl", {"LEFT",self.controls.secondaryAscendDrop,"RIGHT"}, {8, 0, 190, 20}, {}, function(index, value) + -- item, skill, and config sets have identical structure + -- return id as soon as it's found + local function findSetId(setOrderList, value, sets, setSpecialLinks) + for setIndex, setOrder in pairs(setOrderList) do + if value == (sets[setOrder].title or "Default") then + --index = setIndex + return setIndex --setOrder works until you delete something + else + local linkMatch = string.match(value, "%{(%w+)%}") + if linkMatch then + --index = setIndex + return setSpecialLinks[linkMatch]["setId"] + end + end + end + return nil + end + -- trees have a different structure with id/name pairs + -- return id as soon as it's found + local function findNamedSetId(treeList, value, setSpecialLinks) + for id, spec in ipairs(treeList) do + if value == spec then + return id, value + else + local linkMatch = string.match(value, "%{(%w+)%}") + if linkMatch then + return setSpecialLinks[linkMatch]["setId"], string.match(value, "%b{}") -- test {Default} returns {Default} + end + end + end + return nil + end + + local oneSkill = self.skillsTab and #self.skillsTab.skillSetOrderList == 1 + local oneItem = self.itemsTab and #self.itemsTab.itemSetOrderList == 1 + local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1 + + -- *** big block of variables shared across Copy, Delete, and Rename Loadouts + -- generic SetListControls so we can reuse copy/delete/rename functions + local passiveSpecListControl = new ("PassiveSpecListControl", nil, nil, self.treeTab) + local itemSetListControl = new("ItemSetListControl", nil, nil, self.itemsTab) + local skillSetListControl = new("SkillSetListControl", nil, nil, self.skillsTab) + local configSetListControl = new("ConfigSetListControl", nil, nil, self.configTab) + -- list for dropdown + local existingLoadoutsList = self.controls.buildLoadouts.existingLoadoutsList or {} + local selectedLoadoutTitle = existingLoadoutsList[1] or "Default" + -- set indices of each type for selected loadout for copy/delete/rename + local selectedLoadoutTreeId = { } + local selectedLoadoutItemId = { } + local selectedLoadoutSkillId = { } + local selectedLoadoutConfigId = { } + local loadoutTitleOrIdentifier = "" + -- *** + + local function renameSets(title, newFromExisting) + if newFromExisting then + self.treeTab.specList[self.treeTab.activeSpec].title = title + self.itemsTab.itemSets[self.itemsTab.activeItemSetId].title = title + self.skillsTab.skillSets[self.skillsTab.activeSkillSetId].title = title + self.configTab.configSets[self.configTab.activeConfigSetId].title = title + else + self.treeTab.specList[selectedLoadoutTreeId].title = title + --self.itemsTab.itemSets[selectedLoadoutItemId].title = title + for _, id in pairs(self.itemsTab.itemSetOrderList) do + if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then + self.itemsTab.itemSets[id].title = title + break + end + end + --self.skillsTab.skillSets[selectedLoadoutSkillId].title = title + for _, id in pairs(self.skillsTab.skillSetOrderList) do + if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then + self.skillsTab.skillSets[id].title = title + break + end + end + --self.configTab.configSets[selectedLoadoutConfigId].title = title + for _, id in pairs(self.configTab.configSetOrderList) do + if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then + self.configTab.configSets[id].title = title + break + end + end + end + end + local function setSelectedLoadout(title) + selectedLoadoutTreeId, loadoutTitleOrIdentifier = findNamedSetId(self.treeTab:GetSpecList(), title, self.treeListSpecialLinks) + + -- because we are creating the SetListControl in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets + -- so we will loop until we find the title or identifier match from the selected loadout + --selectedLoadoutItemId = findSetId(self.itemsTab.itemSetOrderList, title, self.itemsTab.itemSets, self.itemListSpecialLinks) + for _, id in pairs(self.itemsTab.itemSetOrderList) do + if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then + selectedLoadoutItemId = id + break + end + end + --selectedLoadoutSkillId = findSetId(self.skillsTab.skillSetOrderList, title, self.skillsTab.skillSets, self.skillListSpecialLinks) + for _, id in pairs(self.skillsTab.skillSetOrderList) do + if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then + selectedLoadoutSkillId = id + break + end + end + --selectedLoadoutConfigId = findSetId(self.configTab.configSetOrderList, title, self.configTab.configSets, self.configListSpecialLinks) + for _, id in pairs(self.configTab.configSetOrderList) do + if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then + selectedLoadoutConfigId = id + break + end + end + end + setSelectedLoadout(selectedLoadoutTitle) + if value == "^7^7Loadouts:" or value == "^7^7-----" then self.controls.buildLoadouts:SetSel(1) return - end - if value == "^7^7Sync" then + elseif value == "^7^7Sync" then self:SyncLoadouts() self.controls.buildLoadouts:SetSel(1) return - end - if value == "^7^7Help >>" then + elseif value == "^7^7Help >>" then main:OpenAboutPopup(7) self.controls.buildLoadouts:SetSel(1) return - end - if value == "^7^7New Loadout" then + elseif value == "^7^7New Loadout" then 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) + local createFromExistingSets = false + + controls.loadoutNameLabel = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:") + controls.loadoutName = 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() - local loadout = controls.edit.buf - - local newSpec = new("PassiveSpec", self, latestTreeVersion) - newSpec.title = loadout - t_insert(self.treeTab.specList, newSpec) - - local itemSet = self.itemsTab:NewItemSet(#self.itemsTab.itemSets + 1) - t_insert(self.itemsTab.itemSetOrderList, itemSet.id) - itemSet.title = loadout + controls.existingCheckLabel = new("LabelControl", nil, {-10, 70, 0, 16}, "^7Create a new loadout from current active sets?") + controls.existingCheck = new("CheckBoxControl", {"LEFT",controls.existingCheckLabel,"RIGHT"}, {4, 0, 18}, nil, function(state) + createFromExistingSets = state + end, "If unchecked, a loadout of empty sets will be created.") + controls.save = new("ButtonControl", nil, {-45, 100, 80, 20}, "Create", function() + local loadoutTitle = controls.loadoutName.buf + -- if we're making a loadout out of the existing sets we only need to rename them to match + if createFromExistingSets then + renameSets(loadoutTitle, true) + else + local newSpec = new("PassiveSpec", self, latestTreeVersion) + t_insert(self.treeTab.specList, newSpec) + newSpec.title = loadoutTitle - local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1) - t_insert(self.skillsTab.skillSetOrderList, skillSet.id) - skillSet.title = loadout + local itemSet = self.itemsTab:NewItemSet(#self.itemsTab.itemSets + 1) + t_insert(self.itemsTab.itemSetOrderList, itemSet.id) + itemSet.title = loadoutTitle - local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1) - t_insert(self.configTab.configSetOrderList, configSet.id) - configSet.title = loadout + local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1) + t_insert(self.skillsTab.skillSetOrderList, skillSet.id) + skillSet.title = loadoutTitle + local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1) + t_insert(self.configTab.configSetOrderList, configSet.id) + configSet.title = loadoutTitle + end self:SyncLoadouts() self.modFlag = true main:ClosePopup() end) controls.save.enabled = false - controls.cancel = new("ButtonControl", nil, {45, 70, 80, 20}, "Cancel", function() + controls.cancel = new("ButtonControl", nil, {45, 100, 80, 20}, "Cancel", function() main:ClosePopup() end) - main:OpenPopup(370, 100, "Set Name", controls, "save", "edit", "cancel") + main:OpenPopup(370, 140, "New Loadout", controls, "save", "edit", "cancel") self.controls.buildLoadouts:SetSel(1) return - end + elseif value == "^7^7Rename Loadout" then + local controls = { } - -- item, skill, and config sets have identical structure - -- return id as soon as it's found - local function findSetId(setOrderList, value, sets, setSpecialLinks) - for _, setOrder in ipairs(setOrderList) do - if value == (sets[setOrder].title or "Default") then - return setOrder - else - local linkMatch = string.match(value, "%{(%w+)%}") - if linkMatch then - return setSpecialLinks[linkMatch]["setId"] + controls.loadoutNameLabel = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:") + controls.loadoutName = new("EditControl", nil, { 0, 40, 350, 20}, "New Loadout", nil, nil, 100) + + controls.existingLoadoutsLabel = new("LabelControl", nil, {0, 70, 0, 16}, "^7Choose a loadout to rename:") + controls.existingLoadouts = new("DropDownControl", nil, {0, 90, 190, 20}, existingLoadoutsList, function(index, value) + setSelectedLoadout(value) + end) + + controls.rename = new("ButtonControl", nil, {-45, 125, 80, 20}, "Rename", function() + renameSets(controls.loadoutName.buf) + self:SyncLoadouts() + self.modFlag = true + main:ClosePopup() + end) + controls.rename.enabled = #existingLoadoutsList > 0 + controls.cancel = new("ButtonControl", nil, {45, 125, 80, 20}, "Cancel", function() + main:ClosePopup() + end) + main:OpenPopup(370, 165, "Rename Loadout", controls, "save", "edit", "cancel") + self.controls.buildLoadouts:SetSel(1) + return + elseif value == "^7^7Copy Loadout" then + local controls = { } + local numberofLoadouts = nil + + controls.loadoutNameLabel = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:") + controls.loadoutName = new("EditControl", nil, { 0, 40, 350, 20}, "New Loadout", nil, nil, 100, function(buf) + controls.copy.enabled = buf:match("%S") + end) + + controls.existingLoadoutsLabel = new("LabelControl", nil, {0, 70, 0, 16}, "^7Copy an existing loadout:") + controls.existingLoadouts = new("DropDownControl", nil, {0, 90, 190, 20}, existingLoadoutsList, function(index, value) + setSelectedLoadout(value) + end) + + controls.copy = new("ButtonControl", nil, {-45, 125, 80, 20}, "Copy", function() + local loadoutTitle = controls.loadoutName.buf + + local oldSpec = self.treeTab.specList[selectedLoadoutTreeId] + local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) + t_insert(self.treeTab.specList, newSpec) + newSpec.title = loadoutTitle + + --local newItemSet = itemSetListControl.controls.copy.onClick(selectedLoadoutItemId) + local newItemSet + for _, id in pairs(self.itemsTab.itemSetOrderList) do + if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then + newItemSet = itemSetListControl.controls.copy.onClick(id) + break end end - end - return nil - end - - -- trees have a different structure with id/name pairs - -- return id as soon as it's found - local function findNamedSetId(treeList, value, setSpecialLinks) - for id, spec in ipairs(treeList) do - if value == spec then - return id - else - local linkMatch = string.match(value, "%{(%w+)%}") - if linkMatch then - return setSpecialLinks[linkMatch]["setId"] + t_insert(self.itemsTab.itemSetOrderList, newItemSet.id) + newItemSet.title = loadoutTitle + + --local newSkillSet = skillSetListControl.controls.copy.onClick(selectedLoadoutSkillId) + local newSkillSet + for _, id in pairs(self.skillsTab.skillSetOrderList) do + if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then + newSkillSet = skillSetListControl.controls.copy.onClick(id) + break end end - end - return nil - end + t_insert(self.skillsTab.skillSetOrderList, newSkillSet.id) + newSkillSet.title = loadoutTitle + + --local newConfigSet = configSetListControl.controls.copy.onClick(selectedLoadoutConfigId) + local newConfigSet + for _, id in pairs(self.configTab.configSetOrderList) do + if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then + newConfigSet = configSetListControl.controls.copy.onClick(id) + break + end + end + t_insert(self.configTab.configSetOrderList, newConfigSet.id) + newConfigSet.title = loadoutTitle - local oneSkill = self.skillsTab and #self.skillsTab.skillSetOrderList == 1 - local oneItem = self.itemsTab and #self.itemsTab.itemSetOrderList == 1 - local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1 + self:SyncLoadouts() + self.modFlag = true + main:ClosePopup() + end) + controls.copy.enabled = false + controls.cancel = new("ButtonControl", nil, {45, 125, 80, 20}, "Cancel", function() + main:ClosePopup() + end) + main:OpenPopup(370, 165, "Copy Loadout", controls, "save", "edit", "cancel") + self.controls.buildLoadouts:SetSel(1) + return + elseif value == "^7^7Delete Loadout" then + local controls = { } + + controls.existingLoadoutsLabel = new("LabelControl", nil, {0, 25, 0, 16}, "^7Choose a loadout to delete:") + controls.existingLoadouts = new("DropDownControl", nil, {0, 45, 190, 20}, existingLoadoutsList, function(index, value) + setSelectedLoadout(value) + end) + + controls.delete = new("ButtonControl", nil, {-45, 85, 80, 20}, "Delete", function() + main:OpenConfirmPopup("Delete All", "Are you sure you want to delete this loadout?", "Delete", function() + passiveSpecListControl:DeleteByIndex(selectedLoadoutTreeId) + -- because we are creating the SetListControl in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets + -- so we will loop until we find the title or identifier match from the selected loadout + --itemSetListControl:DeleteById(selectedLoadoutItemId) + for index, id in pairs(self.itemsTab.itemSetOrderList) do + if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then + itemSetListControl:DeleteById(index, id) + break + end + end + --skillSetListControl:DeleteById(selectedLoadoutSkillId) + for index, id in pairs(self.skillsTab.skillSetOrderList) do + if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then + skillSetListControl:DeleteById(index, id) + break + end + end + --configSetListControl:DeleteById(selectedLoadoutConfigId) + for index, id in pairs(self.configTab.configSetOrderList) do + if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then + configSetListControl:DeleteById(index, id) + break + end + end + self:SyncLoadouts() + self.modFlag = true + main:ClosePopup() + end) + end) + controls.delete.enabled = #existingLoadoutsList > 0 + controls.cancel = new("ButtonControl", nil, {45, 85, 80, 20}, "Cancel", function() + main:ClosePopup() + end) + main:OpenPopup(370, 125, "Delete Loadout", controls, "save", "edit", "cancel") + self.controls.buildLoadouts:SetSel(1) + return + end local newSpecId = findNamedSetId(self.treeTab:GetSpecList(), value, self.treeListSpecialLinks) local newItemId = oneItem and 1 or findSetId(self.itemsTab.itemSetOrderList, value, self.itemsTab.itemSets, self.itemListSpecialLinks) @@ -776,7 +996,7 @@ function buildMode:SyncLoadouts() -- item, skill, and config sets have identical structure local function identifyLinks(setOrderList, tabSets, setList, specialLinks, treeLinks) - for id, set in ipairs(setOrderList) do + for id, set in ipairs(setOrderList) do -- I had this as pairs from sometime but I can't remember why local setTitle = tabSets[set].title or "Default" local linkIdentifier = string.match(setTitle, "%{([%w,]+)%}") @@ -818,9 +1038,15 @@ function buildMode:SyncLoadouts() end end + self.controls.buildLoadouts.existingLoadoutsList = copyTable(filteredList) + table.remove(self.controls.buildLoadouts.existingLoadoutsList, 1) -- remove "^7^7Loadouts:" + -- giving the options unique formatting so it can not match with user-created sets t_insert(filteredList, "^7^7-----") t_insert(filteredList, "^7^7New Loadout") + t_insert(filteredList, "^7^7Rename Loadout") + t_insert(filteredList, "^7^7Copy Loadout") + t_insert(filteredList, "^7^7Delete Loadout") t_insert(filteredList, "^7^7Sync") t_insert(filteredList, "^7^7Help >>") From 5a35b2b4256a8575ef401dd728e3ba19c25a5373 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:11:24 -0600 Subject: [PATCH 02/10] bug fix for test case copy -> copy -> rename/delete clean up old comments, remove unused variables refactor loops so we only do it once and set id, index as needed --- src/Classes/ItemSetListControl.lua | 2 - src/Modules/Build.lua | 154 +++++++++-------------------- 2 files changed, 44 insertions(+), 112 deletions(-) diff --git a/src/Classes/ItemSetListControl.lua b/src/Classes/ItemSetListControl.lua index 23cfcbde08..a9481d997e 100644 --- a/src/Classes/ItemSetListControl.lua +++ b/src/Classes/ItemSetListControl.lua @@ -122,7 +122,6 @@ end function ItemSetListClass:OnSelDelete(index, itemSetId) local itemSet = self.itemsTab.itemSets[itemSetId] - --ConPrintf("OnSelDelete, itemSet = "..itemSet.title or "Default"..", index = "..index..", itemSetId = "..itemSetId) if #self.list > 1 then main:OpenConfirmPopup("Delete Item Set", "Are you sure you want to delete '"..(itemSet.title or "Default").."'?\nThis will not delete any items used by the set.", "Delete", function() t_remove(self.list, index) @@ -141,7 +140,6 @@ end -- bypass confirmation popup, used by Loadouts function ItemSetListClass:DeleteById(index, itemSetId, sync) local itemSet = self.itemsTab.itemSets[itemSetId] - --ConPrintf("DeleteById, itemSet = "..itemSet.title or "Default"..", index = "..index..", itemSetId = "..itemSetId) if #self.list > 1 then t_remove(self.list, index) self.itemsTab.itemSets[itemSetId] = nil diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index f7d2be5572..4723513e65 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -332,72 +332,59 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local selectedLoadoutTitle = existingLoadoutsList[1] or "Default" -- set indices of each type for selected loadout for copy/delete/rename local selectedLoadoutTreeId = { } - local selectedLoadoutItemId = { } - local selectedLoadoutSkillId = { } - local selectedLoadoutConfigId = { } + local selectedLoadoutItemId, selectedLoadoutItemIndex = { }, { } -- index needed for delete loadout + local selectedLoadoutSkillId, selectedLoadoutSkillIndex = { }, { } + local selectedLoadoutConfigId, selectedLoadoutConfigIndex = { }, { } local loadoutTitleOrIdentifier = "" -- *** - local function renameSets(title, newFromExisting) - if newFromExisting then - self.treeTab.specList[self.treeTab.activeSpec].title = title - self.itemsTab.itemSets[self.itemsTab.activeItemSetId].title = title - self.skillsTab.skillSets[self.skillsTab.activeSkillSetId].title = title - self.configTab.configSets[self.configTab.activeConfigSetId].title = title - else - self.treeTab.specList[selectedLoadoutTreeId].title = title - --self.itemsTab.itemSets[selectedLoadoutItemId].title = title - for _, id in pairs(self.itemsTab.itemSetOrderList) do - if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then - self.itemsTab.itemSets[id].title = title - break - end - end - --self.skillsTab.skillSets[selectedLoadoutSkillId].title = title - for _, id in pairs(self.skillsTab.skillSetOrderList) do - if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then - self.skillsTab.skillSets[id].title = title - break - end - end - --self.configTab.configSets[selectedLoadoutConfigId].title = title - for _, id in pairs(self.configTab.configSetOrderList) do - if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then - self.configTab.configSets[id].title = title - break - end - end - end - end local function setSelectedLoadout(title) selectedLoadoutTreeId, loadoutTitleOrIdentifier = findNamedSetId(self.treeTab:GetSpecList(), title, self.treeListSpecialLinks) - -- because we are creating the SetListControl in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets -- so we will loop until we find the title or identifier match from the selected loadout - --selectedLoadoutItemId = findSetId(self.itemsTab.itemSetOrderList, title, self.itemsTab.itemSets, self.itemListSpecialLinks) - for _, id in pairs(self.itemsTab.itemSetOrderList) do - if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then + for index, id in pairs(self.itemsTab.itemSetOrderList) do + if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.itemsTab.itemSets[id].title or "Default") == loadoutTitleOrIdentifier) then selectedLoadoutItemId = id + selectedLoadoutItemIndex = index break end end - --selectedLoadoutSkillId = findSetId(self.skillsTab.skillSetOrderList, title, self.skillsTab.skillSets, self.skillListSpecialLinks) - for _, id in pairs(self.skillsTab.skillSetOrderList) do - if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then + for index, id in pairs(self.skillsTab.skillSetOrderList) do + if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.skillsTab.skillSets[id].title or "Default") == loadoutTitleOrIdentifier) then selectedLoadoutSkillId = id + selectedLoadoutSkillIndex = index break end end - --selectedLoadoutConfigId = findSetId(self.configTab.configSetOrderList, title, self.configTab.configSets, self.configListSpecialLinks) - for _, id in pairs(self.configTab.configSetOrderList) do - if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then + for index, id in pairs(self.configTab.configSetOrderList) do + if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.configTab.configSets[id].title or "Default") == loadoutTitleOrIdentifier) then selectedLoadoutConfigId = id + selectedLoadoutConfigIndex = index break end end end setSelectedLoadout(selectedLoadoutTitle) + local function copyLoadout(loadoutTitle) + local oldSpec = self.treeTab.specList[selectedLoadoutTreeId] + local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) + t_insert(self.treeTab.specList, newSpec) + newSpec.title = loadoutTitle + + local newItemSet = itemSetListControl.controls.copy.onClick(selectedLoadoutItemId) + t_insert(self.itemsTab.itemSetOrderList, newItemSet.id) + newItemSet.title = loadoutTitle + + local newSkillSet = skillSetListControl.controls.copy.onClick(selectedLoadoutSkillId) + t_insert(self.skillsTab.skillSetOrderList, newSkillSet.id) + newSkillSet.title = loadoutTitle + + local newConfigSet = configSetListControl.controls.copy.onClick(selectedLoadoutConfigId) + t_insert(self.configTab.configSetOrderList, newConfigSet.id) + newConfigSet.title = loadoutTitle + end + if value == "^7^7Loadouts:" or value == "^7^7-----" then self.controls.buildLoadouts:SetSel(1) return @@ -423,9 +410,9 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end, "If unchecked, a loadout of empty sets will be created.") controls.save = new("ButtonControl", nil, {-45, 100, 80, 20}, "Create", function() local loadoutTitle = controls.loadoutName.buf - -- if we're making a loadout out of the existing sets we only need to rename them to match + -- make a copy so we don't break any existing loadouts if any of their sets are currently active if createFromExistingSets then - renameSets(loadoutTitle, true) + copyLoadout(loadoutTitle) else local newSpec = new("PassiveSpec", self, latestTreeVersion) t_insert(self.treeTab.specList, newSpec) @@ -467,7 +454,12 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end) controls.rename = new("ButtonControl", nil, {-45, 125, 80, 20}, "Rename", function() - renameSets(controls.loadoutName.buf) + local title = controls.loadoutName.buf + self.treeTab.specList[selectedLoadoutTreeId].title = title + self.itemsTab.itemSets[selectedLoadoutItemId].title = title + self.skillsTab.skillSets[selectedLoadoutSkillId].title = title + self.configTab.configSets[selectedLoadoutConfigId].title = title + self:SyncLoadouts() self.modFlag = true main:ClosePopup() @@ -494,46 +486,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end) controls.copy = new("ButtonControl", nil, {-45, 125, 80, 20}, "Copy", function() - local loadoutTitle = controls.loadoutName.buf - - local oldSpec = self.treeTab.specList[selectedLoadoutTreeId] - local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) - t_insert(self.treeTab.specList, newSpec) - newSpec.title = loadoutTitle - - --local newItemSet = itemSetListControl.controls.copy.onClick(selectedLoadoutItemId) - local newItemSet - for _, id in pairs(self.itemsTab.itemSetOrderList) do - if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then - newItemSet = itemSetListControl.controls.copy.onClick(id) - break - end - end - t_insert(self.itemsTab.itemSetOrderList, newItemSet.id) - newItemSet.title = loadoutTitle - - --local newSkillSet = skillSetListControl.controls.copy.onClick(selectedLoadoutSkillId) - local newSkillSet - for _, id in pairs(self.skillsTab.skillSetOrderList) do - if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then - newSkillSet = skillSetListControl.controls.copy.onClick(id) - break - end - end - t_insert(self.skillsTab.skillSetOrderList, newSkillSet.id) - newSkillSet.title = loadoutTitle - - --local newConfigSet = configSetListControl.controls.copy.onClick(selectedLoadoutConfigId) - local newConfigSet - for _, id in pairs(self.configTab.configSetOrderList) do - if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then - newConfigSet = configSetListControl.controls.copy.onClick(id) - break - end - end - t_insert(self.configTab.configSetOrderList, newConfigSet.id) - newConfigSet.title = loadoutTitle - + copyLoadout(controls.loadoutName.buf) self:SyncLoadouts() self.modFlag = true main:ClosePopup() @@ -556,29 +509,10 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.delete = new("ButtonControl", nil, {-45, 85, 80, 20}, "Delete", function() main:OpenConfirmPopup("Delete All", "Are you sure you want to delete this loadout?", "Delete", function() passiveSpecListControl:DeleteByIndex(selectedLoadoutTreeId) - -- because we are creating the SetListControl in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets - -- so we will loop until we find the title or identifier match from the selected loadout - --itemSetListControl:DeleteById(selectedLoadoutItemId) - for index, id in pairs(self.itemsTab.itemSetOrderList) do - if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.itemsTab.itemSets[id].title or "Default" == loadoutTitleOrIdentifier) then - itemSetListControl:DeleteById(index, id) - break - end - end - --skillSetListControl:DeleteById(selectedLoadoutSkillId) - for index, id in pairs(self.skillsTab.skillSetOrderList) do - if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.skillsTab.skillSets[id].title or "Default" == loadoutTitleOrIdentifier) then - skillSetListControl:DeleteById(index, id) - break - end - end - --configSetListControl:DeleteById(selectedLoadoutConfigId) - for index, id in pairs(self.configTab.configSetOrderList) do - if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or (self.configTab.configSets[id].title or "Default" == loadoutTitleOrIdentifier) then - configSetListControl:DeleteById(index, id) - break - end - end + itemSetListControl:DeleteById(selectedLoadoutItemIndex, selectedLoadoutItemId) + skillSetListControl:DeleteById(selectedLoadoutSkillIndex, selectedLoadoutSkillId) + configSetListControl:DeleteById(selectedLoadoutConfigIndex, selectedLoadoutConfigId) + self:SyncLoadouts() self.modFlag = true main:ClosePopup() From fa432e7c0ea5a7513fb5502fd6c4d45a2b86b6cb Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:23:40 -0600 Subject: [PATCH 03/10] remove unused --- src/Modules/Build.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 4723513e65..c77fc1f93e 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -289,12 +289,10 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local function findSetId(setOrderList, value, sets, setSpecialLinks) for setIndex, setOrder in pairs(setOrderList) do if value == (sets[setOrder].title or "Default") then - --index = setIndex - return setIndex --setOrder works until you delete something + return setIndex else local linkMatch = string.match(value, "%{(%w+)%}") if linkMatch then - --index = setIndex return setSpecialLinks[linkMatch]["setId"] end end From 2bd76607da1302f862be1bccc4ed7c3a4a4e3b17 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:51:47 -0600 Subject: [PATCH 04/10] revert index vs order change, index fails if any of the sets are reordered --- src/Modules/Build.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index c77fc1f93e..561da1aec6 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -287,9 +287,9 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin -- item, skill, and config sets have identical structure -- return id as soon as it's found local function findSetId(setOrderList, value, sets, setSpecialLinks) - for setIndex, setOrder in pairs(setOrderList) do + for _, setOrder in ipairs(setOrderList) do if value == (sets[setOrder].title or "Default") then - return setIndex + return setOrder else local linkMatch = string.match(value, "%{(%w+)%}") if linkMatch then From 8fb4424399536140c801f2cfa5ae0f573522ae67 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:15:20 -0600 Subject: [PATCH 05/10] autofocus New, Rename, and Copy input fields --- src/Modules/Build.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 561da1aec6..771cc004f5 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -383,6 +383,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin newConfigSet.title = loadoutTitle end + local popup -- used for SelectControl to autofocus New, Rename, and Copy EditControls if value == "^7^7Loadouts:" or value == "^7^7-----" then self.controls.buildLoadouts:SetSel(1) return @@ -436,7 +437,8 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.cancel = new("ButtonControl", nil, {45, 100, 80, 20}, "Cancel", function() main:ClosePopup() end) - main:OpenPopup(370, 140, "New Loadout", controls, "save", "edit", "cancel") + popup = main:OpenPopup(370, 140, "New Loadout", controls, "save", "edit", "cancel") + popup:SelectControl(controls.loadoutName) self.controls.buildLoadouts:SetSel(1) return @@ -466,7 +468,9 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.cancel = new("ButtonControl", nil, {45, 125, 80, 20}, "Cancel", function() main:ClosePopup() end) - main:OpenPopup(370, 165, "Rename Loadout", controls, "save", "edit", "cancel") + popup = main:OpenPopup(370, 165, "Rename Loadout", controls, "save", "edit", "cancel") + popup:SelectControl(controls.loadoutName) + self.controls.buildLoadouts:SetSel(1) return elseif value == "^7^7Copy Loadout" then @@ -493,7 +497,9 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.cancel = new("ButtonControl", nil, {45, 125, 80, 20}, "Cancel", function() main:ClosePopup() end) - main:OpenPopup(370, 165, "Copy Loadout", controls, "save", "edit", "cancel") + popup = main:OpenPopup(370, 165, "Copy Loadout", controls, "save", "edit", "cancel") + popup:SelectControl(controls.loadoutName) + self.controls.buildLoadouts:SetSel(1) return elseif value == "^7^7Delete Loadout" then From 9b0dc97a8b2e15697a6466103c51219b74549ba4 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:28:32 -0600 Subject: [PATCH 06/10] update help txt --- help.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/help.txt b/help.txt index b728173120..00b5d77268 100644 --- a/help.txt +++ b/help.txt @@ -133,8 +133,9 @@ 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 "New Loadout" option allows the user to create all four sets from a single popup for convenience. If you check the current active sets option, it will make a new loadout by copying all the active sets and giving them the new name. The "Sync" option is a backup option to force the UI to update in case the user has changed this data behind the scenes. +All options that allow you to enter a name only support exact name match, although you can technically add an identifier in the name, but be careful of giving multiple loadouts the same identifier. Things will krangle quickly. ---[Party Tab] From 175a53e2c428852abb0ed6f51221c048e767a5ab Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:53:24 -0600 Subject: [PATCH 07/10] spellchecker --- help.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.txt b/help.txt index 00b5d77268..c1d7d92131 100644 --- a/help.txt +++ b/help.txt @@ -135,7 +135,7 @@ Loadouts can be selected from the dropdown in the top middle of the screen. Sele The "New Loadout" option allows the user to create all four sets from a single popup for convenience. If you check the current active sets option, it will make a new loadout by copying all the active sets and giving them the new name. The "Sync" option is a backup option to force the UI to update in case the user has changed this data behind the scenes. -All options that allow you to enter a name only support exact name match, although you can technically add an identifier in the name, but be careful of giving multiple loadouts the same identifier. Things will krangle quickly. +All options that allow you to enter a name only support exact name match, although you can technically add an identifier in the name, but be careful of giving multiple loadouts the same identifier. Things will get krangled quickly. ---[Party Tab] From 0e38589f0b71a32b5a80e1489929397da6833cfc Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Fri, 6 Mar 2026 07:56:52 -0600 Subject: [PATCH 08/10] init ListControls as needed instead of always --- src/Modules/Build.lua | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 771cc004f5..04fc0c3765 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -320,11 +320,11 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1 -- *** big block of variables shared across Copy, Delete, and Rename Loadouts - -- generic SetListControls so we can reuse copy/delete/rename functions - local passiveSpecListControl = new ("PassiveSpecListControl", nil, nil, self.treeTab) - local itemSetListControl = new("ItemSetListControl", nil, nil, self.itemsTab) - local skillSetListControl = new("SkillSetListControl", nil, nil, self.skillsTab) - local configSetListControl = new("ConfigSetListControl", nil, nil, self.configTab) + -- generic SetListControls so we can reuse copy and delete functions + local passiveSpecListControl = { } + local itemSetListControl = { } + local skillSetListControl = { } + local configSetListControl = { } -- list for dropdown local existingLoadoutsList = self.controls.buildLoadouts.existingLoadoutsList or {} local selectedLoadoutTitle = existingLoadoutsList[1] or "Default" @@ -338,7 +338,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local function setSelectedLoadout(title) selectedLoadoutTreeId, loadoutTitleOrIdentifier = findNamedSetId(self.treeTab:GetSpecList(), title, self.treeListSpecialLinks) - -- because we are creating the SetListControl in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets + -- because we are creating the ListControls in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets -- so we will loop until we find the title or identifier match from the selected loadout for index, id in pairs(self.itemsTab.itemSetOrderList) do if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.itemsTab.itemSets[id].title or "Default") == loadoutTitleOrIdentifier) then @@ -364,7 +364,16 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end setSelectedLoadout(selectedLoadoutTitle) + local function initListControls() + passiveSpecListControl = new ("PassiveSpecListControl", nil, nil, self.treeTab) + itemSetListControl = new("ItemSetListControl", nil, nil, self.itemsTab) + skillSetListControl = new("SkillSetListControl", nil, nil, self.skillsTab) + configSetListControl = new("ConfigSetListControl", nil, nil, self.configTab) + end + local function copyLoadout(loadoutTitle) + initListControls() + local oldSpec = self.treeTab.specList[selectedLoadoutTreeId] local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) t_insert(self.treeTab.specList, newSpec) @@ -475,7 +484,6 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin return elseif value == "^7^7Copy Loadout" then local controls = { } - local numberofLoadouts = nil controls.loadoutNameLabel = new("LabelControl", nil, {0, 20, 0, 16}, "^7Enter name for this loadout:") controls.loadoutName = new("EditControl", nil, { 0, 40, 350, 20}, "New Loadout", nil, nil, 100, function(buf) @@ -512,6 +520,8 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.delete = new("ButtonControl", nil, {-45, 85, 80, 20}, "Delete", function() main:OpenConfirmPopup("Delete All", "Are you sure you want to delete this loadout?", "Delete", function() + initListControls() + passiveSpecListControl:DeleteByIndex(selectedLoadoutTreeId) itemSetListControl:DeleteById(selectedLoadoutItemIndex, selectedLoadoutItemId) skillSetListControl:DeleteById(selectedLoadoutSkillIndex, selectedLoadoutSkillId) From 144fe35b61277c81c2310cff777d5df37e7afdb4 Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:15:36 -0600 Subject: [PATCH 09/10] comment cleanup --- src/Modules/Build.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 04fc0c3765..853206cf15 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -944,7 +944,7 @@ function buildMode:SyncLoadouts() -- item, skill, and config sets have identical structure local function identifyLinks(setOrderList, tabSets, setList, specialLinks, treeLinks) - for id, set in ipairs(setOrderList) do -- I had this as pairs from sometime but I can't remember why + for id, set in ipairs(setOrderList) do local setTitle = tabSets[set].title or "Default" local linkIdentifier = string.match(setTitle, "%{([%w,]+)%}") From 9a8e4823ffaaec84332be33c9d596527c182322d Mon Sep 17 00:00:00 2001 From: Peechey <92683202+Peechey@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:26:05 -0600 Subject: [PATCH 10/10] amaze! I actually wrote a test --- spec/System/TestLoadouts_spec.lua | 117 ++++++++++++++++++++ src/Modules/Build.lua | 175 ++++++++++++++++-------------- 2 files changed, 212 insertions(+), 80 deletions(-) create mode 100644 spec/System/TestLoadouts_spec.lua diff --git a/spec/System/TestLoadouts_spec.lua b/spec/System/TestLoadouts_spec.lua new file mode 100644 index 0000000000..c23a389bf5 --- /dev/null +++ b/spec/System/TestLoadouts_spec.lua @@ -0,0 +1,117 @@ +describe("TestLoadouts", function() + before_each(function() + newBuild() + + build.itemsTab:CreateDisplayItemFromRaw([[Dialla's Malefaction + Sage's Robe + Energy Shield: 95 + EnergyShieldBasePercentile: 0 + Variant: Pre 3.19.0 + Variant: Current + Selected Variant: 2 + Sage's Robe + Quality: 20 + Sockets: R-G-B-B-B-B + LevelReq: 37 + Implicits: 0 + Gems can be Socketed in this Item ignoring Socket Colour + {variant:1}Gems Socketed in Red Sockets have +1 to Level + {variant:2}Gems Socketed in Red Sockets have +2 to Level + {variant:1}Gems Socketed in Green Sockets have +10% to Quality + {variant:2}Gems Socketed in Green Sockets have +30% to Quality + {variant:1}Gems Socketed in Blue Sockets gain 25% increased Experience + {variant:2}Gems Socketed in Blue Sockets gain 100% increased Experience + Has no Attribute Requirements]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + end) + + teardown(function() + -- newBuild() takes care of resetting everything in setup() + end) + + local function getSelectedLoadout(treeId, itemIndex, itemId, skillIndex, skillId, configIndex, configId) + local selectedLoadout = { + treeId = treeId, + itemIndex = itemIndex, + itemId = itemId, + skillIndex = skillIndex, + skillId = skillId, + configIndex = configIndex, + configId = configId, + } + return selectedLoadout + end + + it("Test -- Default loadout exists on new build", function() + assert.are.equals(1, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("Default", build.controls.buildLoadouts.existingLoadoutsList[1]) + end) + + it("Test New Loadout -- Default selected, fromExistingSets false", function() + build:NewLoadout(false, "New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + assert.are.equals(2, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("Default", build.controls.buildLoadouts.existingLoadoutsList[1]) + assert.are.equals("New Loadout 1", build.controls.buildLoadouts.existingLoadoutsList[2]) + + assert.are.equals(1, build.itemsTab.itemSets[1]["Body Armour"].selItemId) + assert.are.equals(0, build.itemsTab.itemSets[2]["Body Armour"].selItemId) -- Dialla's not copied over, new empty set + end) + + it("Test New Loadout -- Default selected, fromExistingSets true", function() + build:NewLoadout(true, "New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + assert.are.equals(2, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("Default", build.controls.buildLoadouts.existingLoadoutsList[1]) + assert.are.equals("New Loadout 1", build.controls.buildLoadouts.existingLoadoutsList[2]) + + assert.are.equals(1, build.itemsTab.itemSets[1]["Body Armour"].selItemId) + assert.are.equals(1, build.itemsTab.itemSets[2]["Body Armour"].selItemId) -- Dialla's copied over successfully + end) + + it("Test Copy Loadout -- Default selected", function() + build:CopyLoadout("New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + assert.are.equals(2, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("Default", build.controls.buildLoadouts.existingLoadoutsList[1]) + assert.are.equals("New Loadout 1", build.controls.buildLoadouts.existingLoadoutsList[2]) + + assert.are.equals(1, build.itemsTab.itemSets[1]["Body Armour"].selItemId) + assert.are.equals(1, build.itemsTab.itemSets[2]["Body Armour"].selItemId) -- Dialla's copied over successfully + end) + + it("Test Rename Loadout -- Default selected", function() + build:RenameLoadout("New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + assert.are.equals(1, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("New Loadout 1", build.controls.buildLoadouts.existingLoadoutsList[1]) + end) + + it("Test Delete Loadout -- Default selected after creating New Loadout 1", function() + build:NewLoadout(false, "New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + build:DeleteLoadout(getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + assert.are.equals(1, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("New Loadout 1", build.controls.buildLoadouts.existingLoadoutsList[1]) + end) + + it("Test Delete Loadout -- New Loadout 1 selected", function() + build:NewLoadout(false, "New Loadout 1", getSelectedLoadout(1, 1, 1, 1, 1, 1, 1)) + build:SyncLoadouts() + + build:DeleteLoadout(getSelectedLoadout(2, 2, 2, 2, 2, 2, 2)) + build:SyncLoadouts() + + assert.are.equals(1, #build.controls.buildLoadouts.existingLoadoutsList) + assert.are.equals("Default", build.controls.buildLoadouts.existingLoadoutsList[1]) + assert.are.equals(1, build.itemsTab.itemSets[1]["Body Armour"].selItemId) -- make sure items weren't somehow affected + end) +end) \ No newline at end of file diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 853206cf15..a0b5c6637a 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -320,78 +320,51 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin local oneConfig = self.configTab and #self.configTab.configSetOrderList == 1 -- *** big block of variables shared across Copy, Delete, and Rename Loadouts - -- generic SetListControls so we can reuse copy and delete functions - local passiveSpecListControl = { } - local itemSetListControl = { } - local skillSetListControl = { } - local configSetListControl = { } -- list for dropdown local existingLoadoutsList = self.controls.buildLoadouts.existingLoadoutsList or {} local selectedLoadoutTitle = existingLoadoutsList[1] or "Default" -- set indices of each type for selected loadout for copy/delete/rename - local selectedLoadoutTreeId = { } - local selectedLoadoutItemId, selectedLoadoutItemIndex = { }, { } -- index needed for delete loadout - local selectedLoadoutSkillId, selectedLoadoutSkillIndex = { }, { } - local selectedLoadoutConfigId, selectedLoadoutConfigIndex = { }, { } + --local selectedLoadout.treeId = { } + --local selectedLoadout.itemId, selectedLoadout.itemIndex = { }, { } -- index needed for delete loadout + --local selectedLoadout.skillId, selectedLoadout.skillIndex = { }, { } + --local selectedLoadout.configId, selectedLoadout.configIndex = { }, { } + + local selectedLoadout = { } + selectedLoadout.treeId = { } + selectedLoadout.itemId, selectedLoadout.itemIndex = { }, { } -- index needed for delete loadout + selectedLoadout.skillId, selectedLoadout.skillIndex = { }, { } + selectedLoadout.configId, selectedLoadout.configIndex = { }, { } local loadoutTitleOrIdentifier = "" -- *** local function setSelectedLoadout(title) - selectedLoadoutTreeId, loadoutTitleOrIdentifier = findNamedSetId(self.treeTab:GetSpecList(), title, self.treeListSpecialLinks) + selectedLoadout.treeId, loadoutTitleOrIdentifier = findNamedSetId(self.treeTab:GetSpecList(), title, self.treeListSpecialLinks) -- because we are creating the ListControls in real time, the SetOrderLists can get out of sync with the ItemSets, SkillSets, ConfigSets -- so we will loop until we find the title or identifier match from the selected loadout for index, id in pairs(self.itemsTab.itemSetOrderList) do if (string.match(self.itemsTab.itemSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.itemsTab.itemSets[id].title or "Default") == loadoutTitleOrIdentifier) then - selectedLoadoutItemId = id - selectedLoadoutItemIndex = index + selectedLoadout.itemId = id + selectedLoadout.itemIndex = index break end end for index, id in pairs(self.skillsTab.skillSetOrderList) do if (string.match(self.skillsTab.skillSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.skillsTab.skillSets[id].title or "Default") == loadoutTitleOrIdentifier) then - selectedLoadoutSkillId = id - selectedLoadoutSkillIndex = index + selectedLoadout.skillId = id + selectedLoadout.skillIndex = index break end end for index, id in pairs(self.configTab.configSetOrderList) do if (string.match(self.configTab.configSets[id].title or "Default", "%b{}") == loadoutTitleOrIdentifier) or ((self.configTab.configSets[id].title or "Default") == loadoutTitleOrIdentifier) then - selectedLoadoutConfigId = id - selectedLoadoutConfigIndex = index + selectedLoadout.configId = id + selectedLoadout.configIndex = index break end end end setSelectedLoadout(selectedLoadoutTitle) - local function initListControls() - passiveSpecListControl = new ("PassiveSpecListControl", nil, nil, self.treeTab) - itemSetListControl = new("ItemSetListControl", nil, nil, self.itemsTab) - skillSetListControl = new("SkillSetListControl", nil, nil, self.skillsTab) - configSetListControl = new("ConfigSetListControl", nil, nil, self.configTab) - end - - local function copyLoadout(loadoutTitle) - initListControls() - - local oldSpec = self.treeTab.specList[selectedLoadoutTreeId] - local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) - t_insert(self.treeTab.specList, newSpec) - newSpec.title = loadoutTitle - - local newItemSet = itemSetListControl.controls.copy.onClick(selectedLoadoutItemId) - t_insert(self.itemsTab.itemSetOrderList, newItemSet.id) - newItemSet.title = loadoutTitle - - local newSkillSet = skillSetListControl.controls.copy.onClick(selectedLoadoutSkillId) - t_insert(self.skillsTab.skillSetOrderList, newSkillSet.id) - newSkillSet.title = loadoutTitle - - local newConfigSet = configSetListControl.controls.copy.onClick(selectedLoadoutConfigId) - t_insert(self.configTab.configSetOrderList, newConfigSet.id) - newConfigSet.title = loadoutTitle - end - local popup -- used for SelectControl to autofocus New, Rename, and Copy EditControls if value == "^7^7Loadouts:" or value == "^7^7-----" then self.controls.buildLoadouts:SetSel(1) @@ -417,27 +390,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin createFromExistingSets = state end, "If unchecked, a loadout of empty sets will be created.") controls.save = new("ButtonControl", nil, {-45, 100, 80, 20}, "Create", function() - local loadoutTitle = controls.loadoutName.buf - -- make a copy so we don't break any existing loadouts if any of their sets are currently active - if createFromExistingSets then - copyLoadout(loadoutTitle) - else - local newSpec = new("PassiveSpec", self, latestTreeVersion) - t_insert(self.treeTab.specList, newSpec) - newSpec.title = loadoutTitle - - local itemSet = self.itemsTab:NewItemSet(#self.itemsTab.itemSets + 1) - t_insert(self.itemsTab.itemSetOrderList, itemSet.id) - itemSet.title = loadoutTitle - - local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1) - t_insert(self.skillsTab.skillSetOrderList, skillSet.id) - skillSet.title = loadoutTitle - - local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1) - t_insert(self.configTab.configSetOrderList, configSet.id) - configSet.title = loadoutTitle - end + self:NewLoadout(createFromExistingSets, controls.loadoutName.buf, selectedLoadout) self:SyncLoadouts() self.modFlag = true main:ClosePopup() @@ -463,12 +416,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end) controls.rename = new("ButtonControl", nil, {-45, 125, 80, 20}, "Rename", function() - local title = controls.loadoutName.buf - self.treeTab.specList[selectedLoadoutTreeId].title = title - self.itemsTab.itemSets[selectedLoadoutItemId].title = title - self.skillsTab.skillSets[selectedLoadoutSkillId].title = title - self.configTab.configSets[selectedLoadoutConfigId].title = title - + self:RenameLoadout(controls.loadoutName.buf, selectedLoadout) self:SyncLoadouts() self.modFlag = true main:ClosePopup() @@ -496,7 +444,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin end) controls.copy = new("ButtonControl", nil, {-45, 125, 80, 20}, "Copy", function() - copyLoadout(controls.loadoutName.buf) + self:CopyLoadout(controls.loadoutName.buf, selectedLoadout) self:SyncLoadouts() self.modFlag = true main:ClosePopup() @@ -520,19 +468,13 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin controls.delete = new("ButtonControl", nil, {-45, 85, 80, 20}, "Delete", function() main:OpenConfirmPopup("Delete All", "Are you sure you want to delete this loadout?", "Delete", function() - initListControls() - - passiveSpecListControl:DeleteByIndex(selectedLoadoutTreeId) - itemSetListControl:DeleteById(selectedLoadoutItemIndex, selectedLoadoutItemId) - skillSetListControl:DeleteById(selectedLoadoutSkillIndex, selectedLoadoutSkillId) - configSetListControl:DeleteById(selectedLoadoutConfigIndex, selectedLoadoutConfigId) - + self:DeleteLoadout(selectedLoadout) self:SyncLoadouts() self.modFlag = true main:ClosePopup() end) end) - controls.delete.enabled = #existingLoadoutsList > 0 + controls.delete.enabled = #existingLoadoutsList > 1 controls.cancel = new("ButtonControl", nil, {45, 85, 80, 20}, "Cancel", function() main:ClosePopup() end) @@ -1030,6 +972,79 @@ function buildMode:SyncLoadouts() return treeList, itemList, skillList, configList end +-- generic SetListControls so we can reuse copy and delete functions for Loadouts +local passiveSpecListControl = { } +local itemSetListControl = { } +local skillSetListControl = { } +local configSetListControl = { } + +function buildMode:NewLoadout(createFromExistingSets, loadoutTitle, selectedLoadout) + -- make a copy so we don't break any existing loadouts if any of their sets are currently active + if createFromExistingSets then + self:CopyLoadout(loadoutTitle, selectedLoadout) + else + local newSpec = new("PassiveSpec", self, latestTreeVersion) + t_insert(self.treeTab.specList, newSpec) + newSpec.title = loadoutTitle + + local itemSet = self.itemsTab:NewItemSet(#self.itemsTab.itemSets + 1) + t_insert(self.itemsTab.itemSetOrderList, itemSet.id) + itemSet.title = loadoutTitle + + local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1) + t_insert(self.skillsTab.skillSetOrderList, skillSet.id) + skillSet.title = loadoutTitle + + local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1) + t_insert(self.configTab.configSetOrderList, configSet.id) + configSet.title = loadoutTitle + end +end + +function buildMode:RenameLoadout(title, selectedLoadout) + self.treeTab.specList[selectedLoadout.treeId].title = title + self.itemsTab.itemSets[selectedLoadout.itemId].title = title + self.skillsTab.skillSets[selectedLoadout.skillId].title = title + self.configTab.configSets[selectedLoadout.configId].title = title +end + +function buildMode:CopyLoadout(loadoutTitle, selectedLoadout) + self:InitLoadoutListControls() + + local oldSpec = self.treeTab.specList[selectedLoadout.treeId] + local newSpec = passiveSpecListControl.controls.copy.onClick(oldSpec) + t_insert(self.treeTab.specList, newSpec) + newSpec.title = loadoutTitle + + local newItemSet = itemSetListControl.controls.copy.onClick(selectedLoadout.itemId) + t_insert(self.itemsTab.itemSetOrderList, newItemSet.id) + newItemSet.title = loadoutTitle + + local newSkillSet = skillSetListControl.controls.copy.onClick(selectedLoadout.skillId) + t_insert(self.skillsTab.skillSetOrderList, newSkillSet.id) + newSkillSet.title = loadoutTitle + + local newConfigSet = configSetListControl.controls.copy.onClick(selectedLoadout.configId) + t_insert(self.configTab.configSetOrderList, newConfigSet.id) + newConfigSet.title = loadoutTitle +end + +function buildMode:DeleteLoadout(selectedLoadout) + self:InitLoadoutListControls() + + passiveSpecListControl:DeleteByIndex(selectedLoadout.treeId) + itemSetListControl:DeleteById(selectedLoadout.itemIndex, selectedLoadout.itemId) + skillSetListControl:DeleteById(selectedLoadout.skillIndex, selectedLoadout.skillId) + configSetListControl:DeleteById(selectedLoadout.configIndex, selectedLoadout.configId) +end + +function buildMode:InitLoadoutListControls() + passiveSpecListControl = new("PassiveSpecListControl", nil, nil, self.treeTab) + itemSetListControl = new("ItemSetListControl", nil, nil, self.itemsTab) + skillSetListControl = new("SkillSetListControl", nil, nil, self.skillsTab) + configSetListControl = new("ConfigSetListControl", nil, nil, self.configTab) +end + function buildMode:EstimatePlayerProgress() local PointsUsed, AscUsed, SecondaryAscUsed = self.spec:CountAllocNodes() local extra = self.calcsTab.mainOutput and self.calcsTab.mainOutput.ExtraPoints or 0