diff --git a/help.txt b/help.txt index b728173120..c1d7d92131 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 get krangled quickly. ---[Party Tab] 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/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..a9481d997e 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 @@ -133,6 +137,24 @@ 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] + 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..a0b5c6637a 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -284,59 +284,6 @@ 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) - if value == "^7^7Loadouts:" or value == "^7^7-----" then - self.controls.buildLoadouts:SetSel(1) - return - end - if value == "^7^7Sync" then - self:SyncLoadouts() - self.controls.buildLoadouts:SetSel(1) - return - end - if value == "^7^7Help >>" then - main:OpenAboutPopup(7) - self.controls.buildLoadouts:SetSel(1) - return - end - if 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) - 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 - - local skillSet = self.skillsTab:NewSkillSet(#self.skillsTab.skillSets + 1) - t_insert(self.skillsTab.skillSetOrderList, skillSet.id) - skillSet.title = loadout - - local configSet = self.configTab:NewConfigSet(#self.configTab.configSets + 1) - t_insert(self.configTab.configSetOrderList, configSet.id) - configSet.title = loadout - - self:SyncLoadouts() - self.modFlag = true - 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") - - self.controls.buildLoadouts:SetSel(1) - return - end - -- item, skill, and config sets have identical structure -- return id as soon as it's found local function findSetId(setOrderList, value, sets, setSpecialLinks) @@ -352,17 +299,16 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin 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 + return id, value else local linkMatch = string.match(value, "%{(%w+)%}") if linkMatch then - return setSpecialLinks[linkMatch]["setId"] + return setSpecialLinks[linkMatch]["setId"], string.match(value, "%b{}") -- test {Default} returns {Default} end end end @@ -373,6 +319,170 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin 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 + -- 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 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) + 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 + 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 + 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 + selectedLoadout.configId = id + selectedLoadout.configIndex = index + break + end + end + end + setSelectedLoadout(selectedLoadoutTitle) + + 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 + elseif value == "^7^7Sync" then + self:SyncLoadouts() + self.controls.buildLoadouts:SetSel(1) + return + elseif value == "^7^7Help >>" then + main:OpenAboutPopup(7) + self.controls.buildLoadouts:SetSel(1) + return + elseif value == "^7^7New Loadout" then + local controls = { } + 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.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() + self:NewLoadout(createFromExistingSets, controls.loadoutName.buf, selectedLoadout) + self:SyncLoadouts() + self.modFlag = true + main:ClosePopup() + end) + controls.save.enabled = false + controls.cancel = new("ButtonControl", nil, {45, 100, 80, 20}, "Cancel", function() + main:ClosePopup() + end) + popup = main:OpenPopup(370, 140, "New Loadout", controls, "save", "edit", "cancel") + popup:SelectControl(controls.loadoutName) + + self.controls.buildLoadouts:SetSel(1) + return + elseif value == "^7^7Rename Loadout" then + local controls = { } + + 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() + self:RenameLoadout(controls.loadoutName.buf, selectedLoadout) + 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) + 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 + local controls = { } + + 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() + self:CopyLoadout(controls.loadoutName.buf, selectedLoadout) + 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) + 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 + 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() + self:DeleteLoadout(selectedLoadout) + self:SyncLoadouts() + self.modFlag = true + main:ClosePopup() + end) + end) + controls.delete.enabled = #existingLoadoutsList > 1 + 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) local newSkillId = oneSkill and 1 or findSetId(self.skillsTab.skillSetOrderList, value, self.skillsTab.skillSets, self.skillListSpecialLinks) @@ -818,9 +928,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 >>") @@ -856,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